shennian 0.4.68 → 0.4.71

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 (95) hide show
  1. package/dist/publish-build-manifest.json +138 -78
  2. package/dist/src/agent-env.js +4 -4
  3. package/dist/src/agents/adapter.d.ts +7 -3
  4. package/dist/src/agents/auth-status.d.ts +4 -0
  5. package/dist/src/agents/auth-status.js +2 -0
  6. package/dist/src/agents/codex-protocol.d.ts +103 -0
  7. package/dist/src/agents/codex-protocol.js +3 -0
  8. package/dist/src/agents/codex-runtime.d.ts +91 -0
  9. package/dist/src/agents/codex-runtime.js +14 -0
  10. package/dist/src/agents/codex.d.ts +15 -91
  11. package/dist/src/agents/codex.js +2 -17
  12. package/dist/src/agents/command-spec.js +4 -3
  13. package/dist/src/agents/detect.js +1 -1
  14. package/dist/src/agents/manager.d.ts +4 -3
  15. package/dist/src/agents/manager.js +4 -4
  16. package/dist/src/agents/model-registry/cache.js +1 -1
  17. package/dist/src/agents/model-registry/discovery.js +3 -3
  18. package/dist/src/agents/model-registry/opencode-api.d.ts +2 -0
  19. package/dist/src/agents/model-registry/opencode-api.js +1 -0
  20. package/dist/src/agents/model-registry/parsers.d.ts +1 -0
  21. package/dist/src/agents/model-registry/parsers.js +4 -4
  22. package/dist/src/agents/model-registry/service.js +1 -1
  23. package/dist/src/agents/opencode.d.ts +4 -0
  24. package/dist/src/agents/opencode.js +1 -1
  25. package/dist/src/commands/agent-readiness.d.ts +17 -0
  26. package/dist/src/commands/agent-readiness.js +2 -0
  27. package/dist/src/commands/agent.js +2 -2
  28. package/dist/src/commands/daemon-instance-lock.js +1 -1
  29. package/dist/src/commands/daemon-process.d.ts +1 -0
  30. package/dist/src/commands/daemon-process.js +1 -1
  31. package/dist/src/commands/daemon-service-spec.d.ts +28 -0
  32. package/dist/src/commands/daemon-service-spec.js +1 -0
  33. package/dist/src/commands/daemon-start-wait.d.ts +1 -1
  34. package/dist/src/commands/daemon-start-wait.js +1 -1
  35. package/dist/src/commands/daemon-state.d.ts +57 -0
  36. package/dist/src/commands/daemon-state.js +1 -0
  37. package/dist/src/commands/daemon-windows.d.ts +8 -0
  38. package/dist/src/commands/daemon-windows.js +10 -10
  39. package/dist/src/commands/daemon.d.ts +4 -64
  40. package/dist/src/commands/daemon.js +13 -13
  41. package/dist/src/commands/upgrade.js +1 -1
  42. package/dist/src/daemon-ipc/agent-readiness.d.ts +14 -0
  43. package/dist/src/daemon-ipc/agent-readiness.js +1 -0
  44. package/dist/src/daemon-ipc/control.d.ts +1 -1
  45. package/dist/src/daemon-ipc/control.js +1 -1
  46. package/dist/src/index.js +3 -3
  47. package/dist/src/native-fusion/local-repository.d.ts +6 -1
  48. package/dist/src/native-fusion/local-repository.js +2 -2
  49. package/dist/src/native-fusion/service.d.ts +2 -0
  50. package/dist/src/native-fusion/service.js +2 -2
  51. package/dist/src/native-fusion/source-visibility-audit.d.ts +16 -0
  52. package/dist/src/native-fusion/source-visibility-audit.js +1 -0
  53. package/dist/src/native-fusion/source-visibility.d.ts +25 -0
  54. package/dist/src/native-fusion/source-visibility.js +1 -0
  55. package/dist/src/native-fusion/state.d.ts +1 -0
  56. package/dist/src/native-fusion/state.js +2 -2
  57. package/dist/src/native-fusion/types.d.ts +5 -0
  58. package/dist/src/room/managed-room-binding.d.ts +2 -0
  59. package/dist/src/room/managed-room-binding.js +1 -1
  60. package/dist/src/room/room-binding-client.d.ts +1 -0
  61. package/dist/src/room/room-binding-client.js +1 -1
  62. package/dist/src/runtime-distribution.d.ts +13 -0
  63. package/dist/src/runtime-distribution.js +1 -0
  64. package/dist/src/security/managed-agent-policy.d.ts +7 -0
  65. package/dist/src/security/managed-agent-policy.js +1 -1
  66. package/dist/src/session/handlers/agent-workspace-validation.d.ts +1 -0
  67. package/dist/src/session/handlers/agent-workspace-validation.js +1 -1
  68. package/dist/src/session/handlers/agents.d.ts +7 -1
  69. package/dist/src/session/handlers/agents.js +1 -1
  70. package/dist/src/session/handlers/chat-context.d.ts +31 -0
  71. package/dist/src/session/handlers/chat-context.js +1 -0
  72. package/dist/src/session/handlers/chat-events.d.ts +1 -1
  73. package/dist/src/session/handlers/chat-events.js +2 -2
  74. package/dist/src/session/handlers/chat-session-lifecycle.d.ts +2 -2
  75. package/dist/src/session/handlers/chat-session-lifecycle.js +1 -1
  76. package/dist/src/session/handlers/chat.d.ts +3 -29
  77. package/dist/src/session/handlers/chat.js +1 -1
  78. package/dist/src/session/manager.js +2 -2
  79. package/dist/src/session/queue-helpers.js +2 -2
  80. package/dist/src/session/queue-message.d.ts +2 -0
  81. package/dist/src/session/queue.d.ts +7 -1
  82. package/dist/src/session/queue.js +1 -1
  83. package/dist/src/session/queued-chat-dispatch.d.ts +4 -1
  84. package/dist/src/session/queued-chat-dispatch.js +1 -1
  85. package/dist/src/session/store.d.ts +4 -2
  86. package/dist/src/session/store.js +2 -2
  87. package/dist/src/session/types.d.ts +1 -0
  88. package/dist/src/upgrade/engine.js +2 -2
  89. package/dist/src/upgrade/scheduler.js +1 -1
  90. package/dist/src/upgrade/updater-launcher.js +3 -3
  91. package/node_modules/@shennian/wire/dist/room.d.ts +2 -0
  92. package/node_modules/@shennian/wire/dist/session-sync.d.ts +2 -1
  93. package/node_modules/@shennian/wire/dist/session.d.ts +19 -1
  94. package/node_modules/@shennian/wire/dist/session.js +8 -0
  95. package/package.json +1 -1
@@ -2,46 +2,10 @@ import { spawn } from 'node:child_process';
2
2
  import type { Command } from 'commander';
3
3
  export { buildWindowsLauncherCommand, buildWindowsScheduledTaskCommands, buildWindowsScheduledTaskSuspendCommands, buildWindowsScheduledTaskXml, buildWindowsStartupVbs, suspendWindowsScheduledTask, } from './daemon-windows.js';
4
4
  export { inferDaemonLauncherFromCommand, isShennianRunServiceCommand } from './daemon-process.js';
5
- export declare function isSafeSnapshotEnvKey(key: string): boolean;
6
- type Platform = 'darwin' | 'linux' | 'win32';
7
- type ServiceLaunchMode = 'direct' | 'global-shim' | 'npx';
8
- type DaemonLauncher = 'desktop-managed' | 'global-cli' | 'unknown';
9
- export type DaemonStatus = {
10
- running: boolean;
11
- pid: number | null;
12
- stale: boolean;
13
- remoteAccessDisabled: boolean;
14
- launcher: DaemonLauncher;
15
- platform: Platform;
16
- shennianDir: string;
17
- pidFile: string;
18
- logFile: string;
19
- machineId?: string;
20
- paired?: boolean;
21
- serverUrl?: string;
22
- instanceId?: string;
23
- adopted?: boolean;
24
- };
25
- export type ServiceLaunchSpec = {
26
- command: string;
27
- args: string[];
28
- mode: ServiceLaunchMode;
29
- };
30
- export type DetachedLaunchSpec = {
31
- command: string;
32
- args: string[];
33
- windowsVerbatimArguments?: boolean;
34
- };
35
- export declare function isEphemeralCliPath(candidate: string): boolean;
36
- export declare function resolveServiceLaunchSpec(input: {
37
- nodeExec: string;
38
- scriptPath: string;
39
- shennianCommandPath?: string | null;
40
- npxPath?: string | null;
41
- desktopBridgePath?: string | null;
42
- }): ServiceLaunchSpec;
43
- export declare function isRemoteAccessDisabled(): boolean;
44
- export declare function clearRemoteAccessDisabled(): void;
5
+ import { type Platform, type DaemonStatus } from './daemon-state.js';
6
+ import { type ServiceLaunchSpec, type DetachedLaunchSpec } from './daemon-service-spec.js';
7
+ export { isRemoteAccessDisabled, clearRemoteAccessDisabled, getCurrentProcessDaemonLauncher, createDaemonInstanceId, writeDaemonPid, clearDaemonPidIfOwner, writeDaemonLauncher, clearDaemonLauncher, recordStartedDaemon, findRunningDaemonProcessIds, isIsolatedDaemonE2eInstance, shouldDiscoverGlobalDaemonProcesses, shouldInstallPlatformService, getDaemonStatus, type DaemonStatus } from './daemon-state.js';
8
+ export { isSafeSnapshotEnvKey, isEphemeralCliPath, resolveServiceLaunchSpec, captureEnvForService, saveEnvSnapshot, type ServiceLaunchSpec, type DetachedLaunchSpec } from './daemon-service-spec.js';
45
9
  export type RemoteAccessPausePreparation = {
46
10
  disabledAt: string;
47
11
  autoStartDisabled: boolean;
@@ -70,26 +34,7 @@ export declare function prepareRemoteAccessPause(options?: RemoteAccessPauseOpti
70
34
  * KeepAlive/Restart policy from relaunching the current daemon in the interim.
71
35
  */
72
36
  export declare function stopPreparedRemoteAccessService(options?: Pick<RemoteAccessPauseOptions, 'platform' | 'servicePresent' | 'execCommand'>): RemoteAccessServiceStopResult;
73
- export declare function getCurrentProcessDaemonLauncher(): DaemonLauncher;
74
- export declare function createDaemonInstanceId(): string;
75
- export declare function writeDaemonPid(pid: number, instanceId: string, meta?: {
76
- version?: string;
77
- launcher?: DaemonLauncher;
78
- adopted?: boolean;
79
- }): void;
80
- export declare function clearDaemonPidIfOwner(pid: number, instanceId: string): void;
81
- export declare function writeDaemonLauncher(pid: number, launcher?: DaemonLauncher, instanceId?: string): void;
82
- export declare function clearDaemonLauncher(instanceId?: string): void;
83
- export declare function recordStartedDaemon(childPid: number | undefined): void;
84
- export declare function findRunningDaemonProcessIds(excludePid?: number): number[];
85
- export declare function isIsolatedDaemonE2eInstance(env?: NodeJS.ProcessEnv, shennianDir?: string, tempDir?: string, defaultDir?: string): boolean;
86
- export declare function shouldDiscoverGlobalDaemonProcesses(env?: NodeJS.ProcessEnv, shennianDir?: string): boolean;
87
- export declare function shouldInstallPlatformService(env?: NodeJS.ProcessEnv, shennianDir?: string): boolean;
88
- export declare function getDaemonStatus(opts?: {
89
- cleanupStale?: boolean;
90
- }): DaemonStatus;
91
37
  export declare function buildDaemonSpawnOptions(launch: DetachedLaunchSpec, logFd: number, env?: NodeJS.ProcessEnv): Parameters<typeof spawn>[2];
92
- export declare function captureEnvForService(): Record<string, string>;
93
38
  export declare function buildMacLaunchdPlist(input?: {
94
39
  launch?: ServiceLaunchSpec;
95
40
  }): string;
@@ -99,11 +44,6 @@ export declare function buildSystemdUnit(): string;
99
44
  type LaunchdCommandExecutor = (command: string) => void;
100
45
  export declare function getLaunchdServiceTarget(uid?: number | string): string;
101
46
  export declare function reloadLaunchdAgent(execCommand?: LaunchdCommandExecutor): boolean;
102
- /**
103
- * Save current env vars so the daemon can load them at startup.
104
- * This is critical for launchd/systemd which start with minimal env.
105
- */
106
- export declare function saveEnvSnapshot(): void;
107
47
  export declare function startDaemonProcess(opts?: {
108
48
  quiet?: boolean;
109
49
  }): void;
@@ -1,13 +1,13 @@
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 ye}from"node:child_process";import{randomUUID as B}from"node:crypto";import{fileURLToPath as we}from"node:url";import{getShennianDir as Ee,loadConfig as V,resolveShennianPath as S,saveConfig as ve}from"../config/index.js";import{buildAugmentedPath as Pe}from"../env-path.js";import{buildWindowsLauncherCommand as Ae,buildWindowsScheduledTaskCommands as Ne,buildWindowsScheduledTaskXml as Ie,buildWindowsStartupVbs as De,escapeXml as T,isWindowsCmdScript as ke,quoteCmdArg as G,quoteSystemdArg as _e,suspendWindowsScheduledTask as be}from"./daemon-windows.js";import{waitForDaemonReadyStatus as x}from"./daemon-start-wait.js";import{findRunningWindowsDaemonProcessIds as Re,inferDaemonLauncherFromCommand as K,isShennianRunServiceCommand as Te,readWindowsProcessCommand as xe}from"./daemon-process.js";import{getCurrentVersion as J}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as xn,buildWindowsScheduledTaskCommands as Ln,buildWindowsScheduledTaskSuspendCommands as On,buildWindowsScheduledTaskXml as Cn,buildWindowsStartupVbs as Hn,suspendWindowsScheduledTask as $n}from"./daemon-windows.js";import{inferDaemonLauncherFromCommand as Un,isShennianRunServiceCommand as jn}from"./daemon-process.js";const Le=a.dirname(we(import.meta.url)),h=Ee(),y=S("daemon.pid"),m=S("daemon.log"),L=S("remote-access.disabled"),k=S("daemon-launcher.json"),q=a.resolve(Le,"../../bin/shennian.js"),X=process.execPath,Y=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 Oe(e){return Y.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 Ce(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)&&(process.env.SHENNIAN_E2E_ISOLATED_DAEMON==="1"||!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 He(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 He(o.readFileSync(y,"utf-8"))}catch{return null}}function v(e){try{return process.kill(e,0),!0}catch{return!1}}function z(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 $e(){try{return o.lstatSync(L),!0}catch(e){return!(e&&typeof e=="object"&&"code"in e&&e.code==="ENOENT")}}function Me(){try{o.unlinkSync(L)}catch(e){if(e&&typeof e=="object"&&"code"in e&&e.code==="ENOENT")return;throw e}}function Ue(e,n){const r=a.dirname(e),t=`${e}.${process.pid}.${B()}.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 je(e={}){const n=e.disabledAt??new Date().toISOString(),r=e.markerFile??L,t=e.platform??p(),s=e.servicePresent??(H()&&U(t)),i=e.execCommand??(l=>{u(l,{stdio:"pipe",windowsHide:!0})});if(Ue(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 We(e={}){const n=e.platform??p();if(!(e.servicePresent??(H()&&U(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 Q(){return process.env.SHENNIAN_DESKTOP_CLI_SCRIPT?"desktop-managed":"global-cli"}function Fe(){return`${process.pid}-${Date.now()}-${B()}`}function Be(e,n,r={}){o.mkdirSync(h,{recursive:!0}),o.writeFileSync(y,JSON.stringify({pid:e,instanceId:n,...r.version?{version:r.version}:{},launcher:r.launcher??Q(),...r.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function _n(e,n){const r=A();if(!(r?.pid!==e||r.instanceId!==n))try{o.unlinkSync(y)}catch{}}function Z(e,n=Q(),r){o.mkdirSync(h,{recursive:!0}),o.writeFileSync(k,JSON.stringify({pid:e,launcher:n,...r?{instanceId:r}:{},updatedAt:new Date().toISOString()},null,2))}function Ve(e){if(e)try{if(JSON.parse(o.readFileSync(k,"utf-8")).instanceId!==e)return}catch{return}try{o.unlinkSync(k)}catch{}}function Ge(e){e&&Z(e)}function Ke(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"){const n=xe(e);return n?K(n):"unknown"}try{const n=u(`ps -p ${e} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");return K(n)}catch{}return"unknown"}function ee(e=process.pid){if(p()==="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(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:Te(i)?s:null}).filter(r=>typeof r=="number")}catch{return[]}}function ne(e=process.env,n=h,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 te(e=process.env,n=h){return!ne(e,n)}function H(e=process.env,n=h){return!ne(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=V();if(s&&e.cleanupStale){try{o.unlinkSync(y)}catch{}n=null,r=null,t=!1,s=!1}if(!t&&te()){const E=ee()[0];if(E&&(r=E,t=!0,s=!1,i=!0,e.cleanupStale)){const F=Fe();Be(E,F,{launcher:C(E),adopted:!0}),Z(E,C(E)),n={pid:E,instanceId:F,adopted:!0}}}return{running:t,pid:r,stale:s,remoteAccessDisabled:$e(),launcher:Ke(r,t),platform:p(),shennianDir:h,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 re(e){const n=e?.trim();if(!n)return;const r=V();r.serverUrl=n,ve(r)}function oe(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const $="com.shennian.agent",g=a.join(d.homedir(),"Library/LaunchAgents",`${$}.plist`),se=S("Shennian.app"),Je=a.join(d.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),_=S("autostart.cmd"),b=S("autostart.vbs"),R=S("autostart.xml"),qe=a.join(Je,"ShennianAgent.vbs");function ie(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 Xe(){const e=p()==="win32"?"npx.cmd":"npx",n=a.join(a.dirname(X),e);return o.existsSync(n)?n:ie("npx")}function N(){return Ce({nodeExec:X,scriptPath:q,shennianCommandPath:ie("shennian"),npxPath:Xe(),desktopBridgePath:process.env.SHENNIAN_DESKTOP_CLI_BRIDGE})}function ce(){try{u(`schtasks /delete /tn "${D}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function ae(){try{o.unlinkSync(qe)}catch{}}function Ye(){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 ze(e){if(p()==="win32"&&ke(e.command)){const n=[e.command,...e.args].map(G).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,r=process.env){return{detached:!0,stdio:["ignore",n,n],env:r,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Ze(){const e=N();o.writeFileSync(_,Ae(e,m)),o.writeFileSync(b,De(_)),ae(),ce();const n=Ie({userId:Ye(),command:a.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[G(b)]});o.writeFileSync(R,n,"utf8");const r=Ne({taskName:D,xmlPath:R});try{return u(r.create,{stdio:"pipe",windowsHide:!0}),u(r.run,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function en(){be(D,e=>{u(e,{stdio:"pipe",windowsHide:!0})})}function ue(){const e={};for(const n of Y)process.env[n]&&(e[n]=process.env[n]);if(e.HOME??=d.homedir(),e.PATH=Pe({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 nn(e={}){const n=ue(),r=e.launch??N(),t=Object.entries(n).map(([i,l])=>` <key>${T(i)}</key>
3
- <string>${T(l)}</string>`).join(`
4
- `),s=[r.command,...r.args].map(i=>` <string>${T(i)}</string>`).join(`
1
+ import i from"chalk";import{isDesktopDistribution as se,readCliDistribution as ie}from"../runtime-distribution.js";import o from"node:fs";import d from"node:path";import g from"node:os";import{execSync as c,spawn as ae}from"node:child_process";import{randomUUID as ce}from"node:crypto";import{fileURLToPath as de}from"node:url";import{loadConfig as ue,resolveShennianPath as k,saveConfig as le}from"../config/index.js";import{buildWindowsLauncherCommand as me,buildWindowsScheduledTaskXml as pe,buildWindowsStartupVbs as fe,escapeXml as A,isWindowsCmdScript as Se,quoteCmdArg as T,quoteSystemdArg as ge,registerWindowsScheduledTask as he,suspendWindowsScheduledTask as ye}from"./daemon-windows.js";import{isDaemonReadyStatus as we,waitForDaemonReadyStatus as $}from"./daemon-start-wait.js";import{getCurrentVersion as O}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as gn,buildWindowsScheduledTaskCommands as hn,buildWindowsScheduledTaskSuspendCommands as yn,buildWindowsScheduledTaskXml as wn,buildWindowsStartupVbs as vn,suspendWindowsScheduledTask as kn}from"./daemon-windows.js";import{inferDaemonLauncherFromCommand as Pn,isShennianRunServiceCommand as bn}from"./daemon-process.js";import{SHENNIAN_DIR as W,PID_FILE as E,LOG_FILE as u,REMOTE_ACCESS_DISABLED_FILE as U,readPidRecord as x,clearDaemonLauncher as ve,shouldDiscoverGlobalDaemonProcesses as ke,getPlatform as m,isRunning as D,waitForPidExitSync as B,isRemoteAccessDisabled as De,clearRemoteAccessDisabled as Pe,recordStartedDaemon as be,findRunningDaemonProcessIds as Ie,shouldInstallPlatformService as R,getDaemonStatus as l}from"./daemon-state.js";import{isEphemeralCliPath as Ae,resolveServiceLaunchSpec as $e,captureEnvForService as F,saveEnvSnapshot as Ee}from"./daemon-service-spec.js";import{isRemoteAccessDisabled as An,clearRemoteAccessDisabled as $n,getCurrentProcessDaemonLauncher as En,createDaemonInstanceId as xn,writeDaemonPid as Rn,clearDaemonPidIfOwner as Ln,writeDaemonLauncher as _n,clearDaemonLauncher as Nn,recordStartedDaemon as Cn,findRunningDaemonProcessIds as jn,isIsolatedDaemonE2eInstance as Tn,shouldDiscoverGlobalDaemonProcesses as On,shouldInstallPlatformService as Wn,getDaemonStatus as Un}from"./daemon-state.js";import{isSafeSnapshotEnvKey as Fn,isEphemeralCliPath as Hn,resolveServiceLaunchSpec as Mn,captureEnvForService as Gn,saveEnvSnapshot as Kn}from"./daemon-service-spec.js";const xe=d.dirname(de(import.meta.url)),H=d.resolve(xe,"../../bin/shennian.js"),M=process.execPath;function G(e,n){const r=d.dirname(e),t=`${e}.${process.pid}.${ce()}.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 a=o.openSync(r,"r");try{o.fsyncSync(a)}finally{o.closeSync(a)}}catch{}}finally{s!==null&&o.closeSync(s);try{o.unlinkSync(t)}catch{}}}function Re(e={}){const n=e.disabledAt??new Date().toISOString(),r=e.markerFile??U,t=e.platform??m(),s=e.servicePresent??(R()&&N(t)),a=e.execCommand??(S=>{c(S,{stdio:"pipe",windowsHide:!0})});if(G(r,n),!s)return{disabledAt:n,autoStartDisabled:!0};try{return a(t==="darwin"?`launchctl disable "${h()}"`:t==="linux"?"systemctl --user disable shennian":`schtasks /change /tn "${v}" /disable`),{disabledAt:n,autoStartDisabled:!0}}catch(S){return{disabledAt:n,autoStartDisabled:!1,autoStartError:S instanceof Error?S.message:String(S)}}}function Le(e={}){const n=e.platform??m();if(!(e.servicePresent??(R()&&N(n))))return{serviceStopped:!0};const t=e.execCommand??(s=>{c(s,{stdio:"pipe",windowsHide:!0})});try{return t(n==="darwin"?`launchctl bootout "${h()}"`:n==="linux"?"systemctl --user stop shennian":`schtasks /end /tn "${v}"`),{serviceStopped:!0}}catch(s){return{serviceStopped:!1,error:s instanceof Error?s.message:String(s)}}}function p(e){process.stdout.write(`${JSON.stringify(e,null,2)}
2
+ `)}function K(e){const n=e?.trim();if(!n)return;const r=ue();r.serverUrl=n,le(r)}function V(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const L="com.shennian.agent",f=d.join(g.homedir(),"Library/LaunchAgents",`${L}.plist`),J=k("Shennian.app"),_e=d.join(g.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),P=k("autostart.cmd"),b=k("autostart.vbs"),I=k("autostart.xml"),Ne=d.join(_e,"ShennianAgent.vbs");function q(e){const n=m()==="win32"?`where ${e}`:`command -v ${e}`;try{return c(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(s=>s.trim()).find(Boolean)??null}catch{return null}}function Ce(){const e=m()==="win32"?"npx.cmd":"npx",n=d.join(d.dirname(M),e);return o.existsSync(n)?n:q("npx")}function y(){return $e({nodeExec:M,scriptPath:H,shennianCommandPath:q("shennian"),npxPath:Ce(),desktopBridgePath:process.env.SHENNIAN_DESKTOP_CLI_BRIDGE})}function X(){try{c(`schtasks /delete /tn "${v}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function Y(){try{o.unlinkSync(Ne)}catch{}}function je(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),r=process.env.USERNAME?.trim()||g.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${r}`:n?`${n}\\${r}`:r}function Te(e){if(m()==="win32"&&Se(e.command)){const n=[e.command,...e.args].map(T).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function Oe(e,n,r=process.env){return{detached:!0,stdio:["ignore",n,n],env:r,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function We(){const e=se(),n=y();o.writeFileSync(P,me(n,u)),o.writeFileSync(b,fe(P)),Y(),e||X();const r=pe({userId:je(),command:d.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[T(b)],desktopManaged:e});return o.writeFileSync(I,`\uFEFF${r}`,"utf16le"),he({taskName:v,xmlPath:I,desktopManaged:e},t=>{c(t,{stdio:"pipe",windowsHide:!0})})}function Ue(){ye(v,e=>{c(e,{stdio:"pipe",windowsHide:!0})})}function Be(e={}){const n=F(),r=e.launch??y(),t=Object.entries(n).map(([a,S])=>` <key>${A(a)}</key>
3
+ <string>${A(S)}</string>`).join(`
4
+ `),s=[r.command,...r.args].map(a=>` <string>${A(a)}</string>`).join(`
5
5
  `);return`<?xml version="1.0" encoding="UTF-8"?>
6
6
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
7
7
  <plist version="1.0">
8
8
  <dict>
9
9
  <key>Label</key>
10
- <string>${$}</string>
10
+ <string>${L}</string>
11
11
  <key>ProgramArguments</key>
12
12
  <array>
13
13
  ${s}
@@ -23,12 +23,12 @@ ${t}
23
23
  <key>ThrottleInterval</key>
24
24
  <integer>5</integer>
25
25
  <key>StandardOutPath</key>
26
- <string>${m}</string>
26
+ <string>${u}</string>
27
27
  <key>StandardErrorPath</key>
28
- <string>${m}</string>
28
+ <string>${u}</string>
29
29
  </dict>
30
- </plist>`}function de(e=se){o.rmSync(e,{recursive:!0,force:!0})}function tn(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 I=a.join(d.homedir(),".config/systemd/user/shennian.service");function rn(){const e=ue(),n=N(),r=Object.entries(e).map(([s,i])=>`Environment=${s}=${i}`).join(`
31
- `),t=[n.command,...n.args].map(_e).join(" ");return`[Unit]
30
+ </plist>`}function z(e=J){o.rmSync(e,{recursive:!0,force:!0})}function Fe(e=m()){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 w=d.join(g.homedir(),".config/systemd/user/shennian.service");function He(){const e=F(),n=y(),r=Object.entries(e).map(([s,a])=>`Environment=${s}=${a}`).join(`
31
+ `),t=[n.command,...n.args].map(ge).join(" ");return`[Unit]
32
32
  Description=Shennian Agent Daemon
33
33
  After=network.target
34
34
 
@@ -37,10 +37,10 @@ ${r}
37
37
  ExecStart=${t}
38
38
  Restart=on-failure
39
39
  RestartSec=10
40
- StandardOutput=append:${m}
41
- StandardError=append:${m}
40
+ StandardOutput=append:${u}
41
+ StandardError=append:${u}
42
42
 
43
43
  [Install]
44
- WantedBy=default.target`}const D="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 P(e=sn()){return`gui/${e}/${$}`}function cn(e=on){try{return e(`launchctl enable "${P()}"; launchctl unload "${g}" 2>/dev/null; launchctl load -w "${g}"`),e(`launchctl kickstart -k "${P()}"`),!0}catch{try{return e(`launchctl enable "${P()}"`),e(`launchctl load -w "${g}"`),e(`launchctl kickstart -k "${P()}"`),!0}catch{return!1}}}function an(){o.mkdirSync(h,{recursive:!0});const e={};for(const[n,r]of Object.entries(process.env))r!==void 0&&Oe(n)&&(e[n]=r);o.writeFileSync(S("env.json"),JSON.stringify(e,null,2))}function le(e={}){o.mkdirSync(h,{recursive:!0});const n=f({cleanupStale:!0});if(n.pid!==null&&n.running&&!M(n)){e.quiet||console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}pe(n);const r=o.openSync(m,"a"),t=ze(N()),s=ye(t.command,t.args,Qe(t,r));s.unref(),o.closeSync(r),Ge(s.pid),e.quiet||(console.log(c.green(`\u2713 Background service started (PID ${s.pid})`)),console.log(c.gray(` Logs: ${m}`)))}function M(e,n=A(),r=J()){return e.pid===null||!e.running||e.launcher==="desktop-managed"?!1:e.adopted===!0||n?.version!==r}function U(e=p()){switch(e){case"darwin":return o.existsSync(g)&&!o.existsSync(se);case"linux":return o.existsSync(I);case"win32":return o.existsSync(_)&&o.existsSync(b)&&o.existsSync(R)}}function un(e,n,r,t=A(),s=J()){return!n.api?.trim()&&e.pid!==null&&e.running&&!e.stale&&!e.remoteAccessDisabled&&(e.launcher==="desktop-managed"||r)&&!M(e,t,s)}function pe(e=f({cleanupStale:!0})){if(!(!M(e)||e.pid===null))try{process.kill(e.pid,"SIGTERM"),z(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),z(e.pid,2e3));try{A()?.pid===e.pid&&o.unlinkSync(y)}catch{}}catch{}}function me(e={}){if(o.mkdirSync(h,{recursive:!0}),!H())return!1;const n=p();switch(N().mode==="direct"&&O(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.")),n){case"darwin":{const t=a.dirname(g);o.mkdirSync(t,{recursive:!0}),de(),o.writeFileSync(g,nn());const s=cn(),i=tn(n);return!e.quiet&&i&&console.log(c.gray(`\u2139 ${i}`)),s}case"linux":{const t=a.dirname(I);o.mkdirSync(t,{recursive:!0}),o.writeFileSync(I,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=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 fe(e=5e3){const n=dn();if(!n.stoppedPid)return n;if(!await W(n.stoppedPid,e))try{process.kill(n.stoppedPid,"SIGKILL"),await W(n.stoppedPid,2e3)}catch(t){return{...n,error:t instanceof Error?t.message:String(t)}}try{o.unlinkSync(y)}catch{}if(Ve(),te()){for(const t of ee())if(t!==n.stoppedPid)try{process.kill(t,"SIGTERM"),await W(t,2e3)}catch{}}return n}function ln(e={}){re(oe(e.api)),Me(),pe(),me({quiet:e.json})||le({quiet:e.json}),e.json?w(x(()=>f({cleanupStale:!0}))):console.log(c.green("\u2713 Background service started"))}async function pn(e={}){const n=je(),r=We(),t=await fe(),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 Se(e){const n=f({cleanupStale:!0});if(un(n,e,U())){e.json?w(n):console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}an(),ln(e)}async function j(e={}){await pn(e)}async function W(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 mn(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}re(oe(e.api));const r=p();r==="win32"&&en();const t=await fe();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")&&me()){e.json&&w(x(()=>f({cleanupStale:!0})));return}le({quiet:e.json}),e.json&&w(x(()=>f({cleanupStale:!0})))}function he(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 ge(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(`
44
+ WantedBy=default.target`}const v="ShennianAgent";function Me(e){c(e,{stdio:"pipe",windowsHide:!0})}function Ge(){const e=process.getuid?.();if(typeof e=="number")return e;const n=g.userInfo();return typeof n.uid=="number"?n.uid:n.username}function h(e=Ge()){return`gui/${e}/${L}`}function Ke(e=Me){try{return e(`launchctl enable "${h()}"; launchctl unload "${f}" 2>/dev/null; launchctl load -w "${f}"`),e(`launchctl kickstart -k "${h()}"`),!0}catch{try{return e(`launchctl enable "${h()}"`),e(`launchctl load -w "${f}"`),e(`launchctl kickstart -k "${h()}"`),!0}catch{return!1}}}function Q(e={}){o.mkdirSync(W,{recursive:!0});const n=l({cleanupStale:!0});if(n.pid!==null&&n.running&&!_(n)){e.quiet||console.log(i.green(`\u2713 Background service already running (PID ${n.pid})`));return}Z(n);const r=o.openSync(u,"a"),t=Te(y()),s=ae(t.command,t.args,Oe(t,r));s.unref(),o.closeSync(r),be(s.pid),e.quiet||(console.log(i.green(`\u2713 Background service started (PID ${s.pid})`)),console.log(i.gray(` Logs: ${u}`)))}function _(e,n=x(),r=O()){return e.pid===null||!e.running||e.launcher==="desktop-managed"?!1:e.adopted===!0||n?.version!==r}function N(e=m()){switch(e){case"darwin":return o.existsSync(f)&&!o.existsSync(J);case"linux":return o.existsSync(w);case"win32":return o.existsSync(P)&&o.existsSync(b)&&o.existsSync(I)}}function Ve(e,n,r,t=x(),s=O()){return!n.api?.trim()&&e.pid!==null&&e.running&&!e.stale&&!e.remoteAccessDisabled&&(e.launcher==="desktop-managed"||r)&&!_(e,t,s)}function Z(e=l({cleanupStale:!0})){if(!_(e)||e.pid===null)return;let n;try{process.kill(e.pid,"SIGTERM"),B(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),B(e.pid,2e3))}catch(r){n=r}if(D(e.pid)){const r=n instanceof Error?n.message:"daemon_stop_timeout";throw new Error(`daemon_replacement_failed: ${r}`)}try{x()?.pid===e.pid&&o.unlinkSync(E)}catch{}}function ee(e={}){if(o.mkdirSync(W,{recursive:!0}),!R())return!1;const n=m();switch(y().mode==="direct"&&Ae(H)&&console.warn(i.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=d.dirname(f);o.mkdirSync(t,{recursive:!0}),z(),o.writeFileSync(f,Be());const s=Ke(),a=Fe(n);return!e.quiet&&a&&console.log(i.gray(`\u2139 ${a}`)),s}case"linux":{const t=d.dirname(w);o.mkdirSync(t,{recursive:!0}),o.writeFileSync(w,He());try{c("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{c(`loginctl enable-linger ${g.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return c("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return We()}}function Je(){const n=l({cleanupStale:!0}).pid;if(n===null)return{};if(!D(n)){try{o.unlinkSync(E)}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 ne(e=5e3){const n=Je();if(!n.stoppedPid)return n;if(!await j(n.stoppedPid,e))try{if(process.kill(n.stoppedPid,"SIGKILL"),!await j(n.stoppedPid,2e3))return{...n,error:"daemon_stop_timeout"}}catch(t){return{...n,error:t instanceof Error?t.message:String(t)}}try{o.unlinkSync(E)}catch{}if(ve(),ke()){for(const t of Ie())if(t!==n.stoppedPid)try{process.kill(t,"SIGTERM"),await j(t,2e3)}catch{}}return n}function qe(e={}){const n=De();try{K(V(e.api)),Pe(),Z(),ee({quiet:e.json})||Q({quiet:e.json});const t=$(()=>l({cleanupStale:!0}));if(!we(t))throw new Error("daemon_start_failed");e.json?p(t):console.log(i.green("\u2713 Background service started"))}catch(r){n&&G(U,new Date().toISOString()),process.exitCode=1;const t=r instanceof Error?r.message:String(r);e.json?p({...l(),error:t}):console.error(i.red(`Background service did not start: ${t}`))}}async function Xe(e={}){const n=Re(),r=Le(),t=await ne(),s=l({cleanupStale:!0}),a=t.error??n.autoStartError??r.error??(s.running?"daemon_stop_failed":s.remoteAccessDisabled?void 0:"remote_access_pause_failed");if(a&&(process.exitCode=1),e.json){p(a?{...s,error:a}:s);return}a?console.error(i.red(`\u2717 Failed to stop completely: ${a}`)):console.log(i.green("\u2713 Background service stopped"))}function te(e){const n=l({cleanupStale:!0});if(Ve(n,e,N())){e.json?p(n):console.log(i.green(`\u2713 Background service already running (PID ${n.pid})`));return}Ee(),qe(e)}async function C(e={}){await Xe(e)}async function j(e,n=5e3){const r=Date.now();for(;Date.now()-r<n;){if(!D(e))return!0;await new Promise(t=>setTimeout(t,100))}return!D(e)}async function Ye(e={}){const n=l({cleanupStale:!0});if(n.remoteAccessDisabled){e.json?p({...n,error:"remote_access_disabled_use_shennian_start"}):console.error(i.yellow("Remote access is paused. Run `shennian start` on this computer to resume."));return}K(V(e.api));const r=m();r==="win32"&&Ue();const t=await ne();if(t.error){e.json?p({...l(),error:t.error}):console.error(i.red(`\u2717 Failed to stop: ${t.error}`));return}if(t.stoppedPid!==void 0?e.json||console.log(i.green(`\u2713 Background service stopped (PID ${t.stoppedPid})`)):t.stalePid!==void 0?e.json||console.log(i.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${t.stalePid}`)):e.json||console.log(i.gray("\u25CF Background service not running, starting it now")),(r==="linux"||r==="win32")&&ee()){e.json&&p($(()=>l({cleanupStale:!0})));return}Q({quiet:e.json}),e.json&&p($(()=>l({cleanupStale:!0})))}function re(e={}){const n={...l(),client:ie()};if(e.json){p(n);return}if(n.pid===null){console.log(i.gray("\u25CF Background service not running"));return}n.running?(console.log(i.green(`\u25CF Background service running (PID ${n.pid})`)),console.log(i.gray(` Logs: ${u}`))):console.log(i.yellow(`\u25CF Background service stopped (PID ${n.pid} is stale)`))}function oe(e){if(!o.existsSync(u)){console.log(i.gray("No logs yet"));return}try{const n=c(g.platform()==="win32"?`powershell Get-Content -Tail ${e.lines} "${u}"`:`tail -n ${e.lines} "${u}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(n)}catch{const n=o.readFileSync(u,"utf-8").split(`
45
45
  `).slice(-e.lines);console.log(n.join(`
46
- `))}}async function fn(){switch(await j(),p()){case"darwin":{if(o.existsSync(g)){try{u(`launchctl unload -w "${g}"`,{stdio:"pipe",windowsHide:!0})}catch{}o.unlinkSync(g)}de(),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(I)){o.unlinkSync(I);try{u("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(c.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{ce();try{o.unlinkSync(_)}catch{}try{o.unlinkSync(b)}catch{}try{o.unlinkSync(R)}catch{}ae(),console.log(c.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function bn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(t=>Se(t)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(t=>j(t)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>he(t)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>ge({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),Se(t)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async t=>{r("stop",t.json),await j(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),mn(t)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>{r("status",t.json),he(t)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>{r("logs"),ge({lines:parseInt(t.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(fn)}export{Qe as buildDaemonSpawnOptions,nn as buildMacLaunchdPlist,rn as buildSystemdUnit,xn as buildWindowsLauncherCommand,Ln as buildWindowsScheduledTaskCommands,On as buildWindowsScheduledTaskSuspendCommands,Cn as buildWindowsScheduledTaskXml,Hn as buildWindowsStartupVbs,un as canReuseRunningDaemonForStart,ue as captureEnvForService,Ve as clearDaemonLauncher,_n as clearDaemonPidIfOwner,Me as clearRemoteAccessDisabled,Fe as createDaemonInstanceId,ee as findRunningDaemonProcessIds,Q as getCurrentProcessDaemonLauncher,f as getDaemonStatus,P as getLaunchdServiceTarget,tn as getMacBackgroundItemNotice,Un as inferDaemonLauncherFromCommand,me as installService,U as isAutoStartServicePresent,O as isEphemeralCliPath,ne as isIsolatedDaemonE2eInstance,$e as isRemoteAccessDisabled,Oe as isSafeSnapshotEnvKey,jn as isShennianRunServiceCommand,je as prepareRemoteAccessPause,Ge as recordStartedDaemon,bn as registerDaemonCommand,cn as reloadLaunchdAgent,de as removeLegacyMacBackgroundApp,Ce as resolveServiceLaunchSpec,an as saveEnvSnapshot,te as shouldDiscoverGlobalDaemonProcesses,H as shouldInstallPlatformService,M as shouldReplaceRunningDaemon,le as startDaemonProcess,We as stopPreparedRemoteAccessService,$n as suspendWindowsScheduledTask,Z as writeDaemonLauncher,Be as writeDaemonPid};
46
+ `))}}async function ze(){switch(await C(),m()){case"darwin":{if(o.existsSync(f)){try{c(`launchctl unload -w "${f}"`,{stdio:"pipe",windowsHide:!0})}catch{}o.unlinkSync(f)}z(),console.log(i.green("\u2713 Auto-start uninstalled (launchd)"));break}case"linux":{try{c("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(o.existsSync(w)){o.unlinkSync(w);try{c("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(i.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{X();try{o.unlinkSync(P)}catch{}try{o.unlinkSync(b)}catch{}try{o.unlinkSync(I)}catch{}Y(),console.log(i.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function pn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(t=>te(t)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(t=>C(t)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>re(t)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>oe({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(i.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),te(t)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async t=>{r("stop",t.json),await C(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),Ye(t)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>{r("status",t.json),re(t)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>{r("logs"),oe({lines:parseInt(t.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(ze)}export{Oe as buildDaemonSpawnOptions,Be as buildMacLaunchdPlist,He as buildSystemdUnit,gn as buildWindowsLauncherCommand,hn as buildWindowsScheduledTaskCommands,yn as buildWindowsScheduledTaskSuspendCommands,wn as buildWindowsScheduledTaskXml,vn as buildWindowsStartupVbs,Ve as canReuseRunningDaemonForStart,Gn as captureEnvForService,Nn as clearDaemonLauncher,Ln as clearDaemonPidIfOwner,$n as clearRemoteAccessDisabled,xn as createDaemonInstanceId,jn as findRunningDaemonProcessIds,En as getCurrentProcessDaemonLauncher,Un as getDaemonStatus,h as getLaunchdServiceTarget,Fe as getMacBackgroundItemNotice,Pn as inferDaemonLauncherFromCommand,ee as installService,N as isAutoStartServicePresent,Hn as isEphemeralCliPath,Tn as isIsolatedDaemonE2eInstance,An as isRemoteAccessDisabled,Fn as isSafeSnapshotEnvKey,bn as isShennianRunServiceCommand,Re as prepareRemoteAccessPause,Cn as recordStartedDaemon,pn as registerDaemonCommand,Ke as reloadLaunchdAgent,z as removeLegacyMacBackgroundApp,Mn as resolveServiceLaunchSpec,Kn as saveEnvSnapshot,On as shouldDiscoverGlobalDaemonProcesses,Wn as shouldInstallPlatformService,_ as shouldReplaceRunningDaemon,Q as startDaemonProcess,Le as stopPreparedRemoteAccessService,kn as suspendWindowsScheduledTask,_n as writeDaemonLauncher,Rn as writeDaemonPid};
@@ -1 +1 @@
1
- import c from"chalk";import{execSync as P,spawn as V}from"node:child_process";import{randomUUID as w}from"node:crypto";import $ from"node:os";import{createInterface as A}from"node:readline/promises";import{getCurrentVersion as y,fetchLatestVersion as k,compareVersions as v,checkForUpdate as M,acquireUpgradeLock as F,recordUpgradeFailure as O}from"../upgrade/engine.js";import{callDaemonIpc as _}from"../daemon-ipc/client.js";import{getDaemonStatus as D}from"./daemon.js";import{ensureUpdaterInstalled as U}from"../upgrade/updater-bootstrap.js";import{launchUpdater as S,createUpdaterJob as R,waitForUpdaterReady as H,confirmUpdaterHandoff as L}from"../upgrade/updater-launcher.js";import{prepareRecoveryServiceEntry as j}from"../upgrade/updater-service-entry.js";import{resolveShennianPath as q}from"../config/index.js";import{assertUpdaterAvailable as N,prepareUpdaterCandidate as C}from"../upgrade/updater-worker.js";import{getUpgradeChannel as x,setUpgradeChannel as G}from"../upgrade/channel.js";import{fetchOwnedCliArtifact as T,getCliUpdateSourceOrder as b}from"../upgrade/distribution.js";const I=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function Y(t,r){return t!=="major"||r}function ie(t={}){const r=t.platform??$.platform(),i=t.kill??process.kill,e=t.pid??process.pid,l=t.env??process.env,n=t.spawn??V,o=t.execSync??P;if(r==="win32"){const a=l.ComSpec||"cmd.exe";try{n(a,["/d","/s","/c","timeout /t 2 /nobreak >nul & schtasks /Run /TN ShennianAgent >nul"],{detached:!0,stdio:"ignore",windowsHide:!0}).unref(),i(e,"SIGTERM");return}catch{try{o('schtasks /run /tn "ShennianAgent"',{stdio:"pipe",windowsHide:!0})}catch{}}}else try{n("sh",["-c","sleep 2; shennian start >/dev/null 2>&1"],{detached:!0,stdio:"ignore",env:l}).unref()}catch{}i(e,"SIGTERM")}function ce(t){t.command("upgrade").description("Upgrade shennian CLI to the latest version").option("--channel <channel>","Persist stable (npm latest) or preview (npm next); does not install").option("--check","Only check for updates, do not install").option("--to <version>","Upgrade to a specific version").option("--yes","Confirm a major version upgrade (required in non-interactive environments)").action(async r=>{if(r.channel!==void 0){if(r.to||r.yes)throw new Error("--channel cannot be combined with --to or --yes");if(G(r.channel),console.log(`Upgrade channel: ${x()} (saved; no installation)`),!r.check)return}console.log(`Upgrade channel: ${x()}`);const i=y();console.log(c.gray(`Current version: ${i}`)),console.log();let e=0;const l=setInterval(()=>{process.stdout.write(`\r ${I[e%I.length]} Checking for updates...`),e++},80);let n;try{n=r.to??await k(),clearInterval(l),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(a){clearInterval(l),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(c.red(`\u2717 Failed to check for updates: ${a instanceof Error?a.message:String(a)}`)),process.exit(1)}const o=v(i,n);if(o==="none"&&!r.to){console.log(c.green(`\u2713 Already up to date (${i})`));return}if(console.log(` Available: ${c.green(n)} ${o==="major"?c.red("(major)"):o==="minor"?c.yellow("(minor)"):c.green("(patch)")}`),console.log(),!r.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(),!r.yes)){(!process.stdin.isTTY||!process.stdout.isTTY)&&(console.error(c.red("\u2717 Major upgrades require explicit confirmation; rerun with --yes.")),process.exit(1));const a=A({input:process.stdin,output:process.stdout}),p=await a.question(` Type ${n} to confirm this major upgrade: `);if(a.close(),p.trim()!==n){console.log(c.gray(" Upgrade cancelled."));return}}try{await _({method:"upgrade.start",params:{targetVersion:n,confirmedMajor:o==="major"||r.yes===!0},timeoutMs:1e4})}catch(a){if(D({cleanupStale:!0}).running)throw a;const m=U(),u={fromVersion:i,targetVersion:n,oldDaemonPid:null,jobId:w()},g=R(u);await C(g,{fetchOwnedArtifact:T,sourceOrder:b()}),j(g,m.workerPath),S(u,{readiness:m})}console.log(),console.log(c.green(`\u2713 Upgrade scheduled ${i} \u2192 ${n}`)),console.log(c.gray(" The independent updater will verify, roll back if needed, and restart Shennian."))}})}async function de(t,r,i,e={}){const l=s=>{t.sendEvent({type:"event",event:"upgrade",payload:s})},n=e.currentVersion??y();let o;try{l({state:"checking",machineId:"self"}),o=i??await k()}catch(s){const d=`Version check failed: ${s instanceof Error?s.message:String(s)}`;return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!1,error:d}),{ok:!1,error:d}}const a=v(n,o);if(a==="none")return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!0,payload:{alreadyLatest:!0,version:n}}),{ok:!0,alreadyLatest:!0,version:n};if(!Y(a,e.confirmedMajor===!0))return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!1,error:"Major upgrades require explicit second confirmation"}),{ok:!1,error:"Major upgrades require explicit second confirmation"};const p=w();let m;try{m=F();try{N(q("runtime","updater"))}catch(s){throw m.release(),s}}catch(s){const d=s instanceof Error?s.message:String(s);return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!1,error:d}),{ok:!1,error:d}}const u={from:n,to:o,jobId:p},g=async()=>{let s=!1;try{const d=U(),f={fromVersion:n,targetVersion:o,oldDaemonPid:process.pid,jobId:p,requireHandoffAck:!0},h=R(f);if(l({state:"downloading",machineId:"self",version:o,jobId:p}),await C(h,{fetchOwnedArtifact:T,sourceOrder:b()}),j(h,d.workerPath),S(f,{readiness:d}),await H(h),e.canHandoff&&!e.canHandoff())throw new Error("upgrade_waiting_for_idle");const E=e.prepareForUpgrade?.(p)??{activeRunCount:0};return s=!0,L(h),l({state:"restarting",machineId:"self",...u,activeRunCount:E.activeRunCount}),{ok:!0,...u,resultPath:h.resultPath}}catch(d){s&&e.cancelUpgradePreparation?.();const f=d instanceof Error?d.message:String(d);return f!=="upgrade_waiting_for_idle"&&O(o,f),l({state:"failed",machineId:"self",error:f,jobId:p}),{ok:!1,error:f}}finally{m.release()}};return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!0,payload:u}),e.waitForCompletion?g():(g(),{ok:!0,...u})}async function le(t,r,i={}){const e=await M(i.currentVersion);t.sendRes({type:"res",id:r,ok:!0,payload:e.hasUpdate?{hasUpdate:!0,current:e.current,latest:e.latest,changeType:e.changeType}:{hasUpdate:!1,current:i.currentVersion??y()}})}export{de as handleUpgradeStart,le as handleUpgradeStatus,Y as hasMajorUpgradeConfirmation,ce as registerUpgradeCommand,ie as restartCurrentDaemonAfterUpgrade};
1
+ import c from"chalk";import{assertStandaloneUpgrade as V,DESKTOP_UPGRADE_REQUIRED as w,isDesktopDistribution as $}from"../runtime-distribution.js";import{execSync as A,spawn as D}from"node:child_process";import{randomUUID as k}from"node:crypto";import M from"node:os";import{createInterface as _}from"node:readline/promises";import{getCurrentVersion as y,fetchLatestVersion as v,compareVersions as U,checkForUpdate as F,acquireUpgradeLock as O,recordUpgradeFailure as H}from"../upgrade/engine.js";import{callDaemonIpc as L}from"../daemon-ipc/client.js";import{getDaemonStatus as G}from"./daemon.js";import{ensureUpdaterInstalled as R}from"../upgrade/updater-bootstrap.js";import{launchUpdater as S,createUpdaterJob as j,waitForUpdaterReady as N,confirmUpdaterHandoff as q}from"../upgrade/updater-launcher.js";import{prepareRecoveryServiceEntry as C}from"../upgrade/updater-service-entry.js";import{resolveShennianPath as Y}from"../config/index.js";import{assertUpdaterAvailable as z,prepareUpdaterCandidate as x}from"../upgrade/updater-worker.js";import{getUpgradeChannel as E,setUpgradeChannel as J}from"../upgrade/channel.js";import{fetchOwnedCliArtifact as T,getCliUpdateSourceOrder as b}from"../upgrade/distribution.js";const I=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function K(t,r){return t!=="major"||r}function pe(t={}){const r=t.platform??M.platform(),i=t.kill??process.kill,e=t.pid??process.pid,l=t.env??process.env,n=t.spawn??D,o=t.execSync??A;if(r==="win32"){const a=l.ComSpec||"cmd.exe";try{n(a,["/d","/s","/c","timeout /t 2 /nobreak >nul & schtasks /Run /TN ShennianAgent >nul"],{detached:!0,stdio:"ignore",windowsHide:!0}).unref(),i(e,"SIGTERM");return}catch{try{o('schtasks /run /tn "ShennianAgent"',{stdio:"pipe",windowsHide:!0})}catch{}}}else try{n("sh",["-c","sleep 2; shennian start >/dev/null 2>&1"],{detached:!0,stdio:"ignore",env:l}).unref()}catch{}i(e,"SIGTERM")}function ue(t){t.command("upgrade").description("Upgrade shennian CLI to the latest version").option("--channel <channel>","Persist stable (npm latest) or preview (npm next); does not install").option("--check","Only check for updates, do not install").option("--to <version>","Upgrade to a specific version").option("--yes","Confirm a major version upgrade (required in non-interactive environments)").action(async r=>{if(V(),r.channel!==void 0){if(r.to||r.yes)throw new Error("--channel cannot be combined with --to or --yes");if(J(r.channel),console.log(`Upgrade channel: ${E()} (saved; no installation)`),!r.check)return}console.log(`Upgrade channel: ${E()}`);const i=y();console.log(c.gray(`Current version: ${i}`)),console.log();let e=0;const l=setInterval(()=>{process.stdout.write(`\r ${I[e%I.length]} Checking for updates...`),e++},80);let n;try{n=r.to??await v(),clearInterval(l),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(a){clearInterval(l),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(c.red(`\u2717 Failed to check for updates: ${a instanceof Error?a.message:String(a)}`)),process.exit(1)}const o=U(i,n);if(o==="none"&&!r.to){console.log(c.green(`\u2713 Already up to date (${i})`));return}if(console.log(` Available: ${c.green(n)} ${o==="major"?c.red("(major)"):o==="minor"?c.yellow("(minor)"):c.green("(patch)")}`),console.log(),!r.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(),!r.yes)){(!process.stdin.isTTY||!process.stdout.isTTY)&&(console.error(c.red("\u2717 Major upgrades require explicit confirmation; rerun with --yes.")),process.exit(1));const a=_({input:process.stdin,output:process.stdout}),p=await a.question(` Type ${n} to confirm this major upgrade: `);if(a.close(),p.trim()!==n){console.log(c.gray(" Upgrade cancelled."));return}}try{await L({method:"upgrade.start",params:{targetVersion:n,confirmedMajor:o==="major"||r.yes===!0},timeoutMs:1e4})}catch(a){if(G({cleanupStale:!0}).running)throw a;const m=R(),u={fromVersion:i,targetVersion:n,oldDaemonPid:null,jobId:k()},g=j(u);await x(g,{fetchOwnedArtifact:T,sourceOrder:b()}),C(g,m.workerPath),S(u,{readiness:m})}console.log(),console.log(c.green(`\u2713 Upgrade scheduled ${i} \u2192 ${n}`)),console.log(c.gray(" The independent updater will verify, roll back if needed, and restart Shennian."))}})}async function fe(t,r,i,e={}){if($())return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!1,error:w}),{ok:!1,error:w};const l=s=>{t.sendEvent({type:"event",event:"upgrade",payload:s})},n=e.currentVersion??y();let o;try{l({state:"checking",machineId:"self"}),o=i??await v()}catch(s){const d=`Version check failed: ${s instanceof Error?s.message:String(s)}`;return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!1,error:d}),{ok:!1,error:d}}const a=U(n,o);if(a==="none")return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!0,payload:{alreadyLatest:!0,version:n}}),{ok:!0,alreadyLatest:!0,version:n};if(!K(a,e.confirmedMajor===!0))return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!1,error:"Major upgrades require explicit second confirmation"}),{ok:!1,error:"Major upgrades require explicit second confirmation"};const p=k();let m;try{m=O();try{z(Y("runtime","updater"))}catch(s){throw m.release(),s}}catch(s){const d=s instanceof Error?s.message:String(s);return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!1,error:d}),{ok:!1,error:d}}const u={from:n,to:o,jobId:p},g=async()=>{let s=!1;try{const d=R(),f={fromVersion:n,targetVersion:o,oldDaemonPid:process.pid,jobId:p,requireHandoffAck:!0},h=j(f);if(l({state:"downloading",machineId:"self",version:o,jobId:p}),await x(h,{fetchOwnedArtifact:T,sourceOrder:b()}),C(h,d.workerPath),S(f,{readiness:d}),await N(h),e.canHandoff&&!e.canHandoff())throw new Error("upgrade_waiting_for_idle");const P=e.prepareForUpgrade?.(p)??{activeRunCount:0};return s=!0,q(h),l({state:"restarting",machineId:"self",...u,activeRunCount:P.activeRunCount}),{ok:!0,...u,resultPath:h.resultPath}}catch(d){s&&e.cancelUpgradePreparation?.();const f=d instanceof Error?d.message:String(d);return f!=="upgrade_waiting_for_idle"&&H(o,f),l({state:"failed",machineId:"self",error:f,jobId:p}),{ok:!1,error:f}}finally{m.release()}};return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!0,payload:u}),e.waitForCompletion?g():(g(),{ok:!0,...u})}async function me(t,r,i={}){const e=await F(i.currentVersion);t.sendRes({type:"res",id:r,ok:!0,payload:e.hasUpdate?{hasUpdate:!0,current:e.current,latest:e.latest,changeType:e.changeType}:{hasUpdate:!1,current:i.currentVersion??y()}})}export{fe as handleUpgradeStart,me as handleUpgradeStatus,K as hasMajorUpgradeConfirmation,ue as registerUpgradeCommand,pe as restartCurrentDaemonAfterUpgrade};
@@ -0,0 +1,14 @@
1
+ import { type AgentInfo } from '@shennian/wire';
2
+ import type { DaemonStatus } from '../commands/daemon.js';
3
+ import type { AgentWorkspaceValidationInput, AgentWorkspaceValidationResult } from '../session/handlers/agent-workspace-validation.js';
4
+ import type { DaemonIpcHandler } from './server.js';
5
+ type DiscoveredAi = AgentInfo & {
6
+ runtimePlatform?: string;
7
+ runtimeIsWsl?: boolean;
8
+ };
9
+ export declare function createAgentReadinessHandlers(input: {
10
+ getStatus: () => DaemonStatus;
11
+ discover: () => Promise<DiscoveredAi[]>;
12
+ validate: (params: AgentWorkspaceValidationInput) => Promise<AgentWorkspaceValidationResult>;
13
+ }): Record<string, DaemonIpcHandler>;
14
+ export {};
@@ -0,0 +1 @@
1
+ import{isAvailableAgentType as s}from"@shennian/wire";import{DaemonIpcError as n}from"./protocol.js";function u(i){let r=!1;const l=()=>{const e=i.getStatus();if(e.remoteAccessDisabled)throw new n("daemon_paused","Shennian is paused; resume it only when requested.");if(!e.running)throw new n("daemon_unavailable","Shennian is not running.")};return{"agent.discover":async()=>(l(),{modelTested:!1,agents:(await i.discover()).map(t=>({type:t.type,name:t.display?.name??t.type,authState:t.authState??"unknown",...t.runtime?.cliVersion?{version:t.runtime.cliVersion}:{},...t.runtime?.compatibility?{compatibility:t.runtime.compatibility}:{},...t.runtimePlatform?{runtimePlatform:t.runtimePlatform}:{},...t.runtimeIsWsl!==void 0?{runtimeIsWsl:t.runtimeIsWsl}:{},models:t.models.map(o=>({id:o.id,name:o.name,isDefault:o.isDefault===!0}))}))}),"agent.check":async e=>{if(l(),!e||typeof e!="object"||Array.isArray(e)||typeof e.agentType!="string"||!s(e.agentType)||typeof e.workDir!="string"||!e.workDir.trim()||e.modelId!=null&&typeof e.modelId!="string")throw new n("agent_validation_invalid_configuration","Select an AI and its absolute working directory.");if(r)throw new n("agent_check_in_progress","An AI reply check is already running.");r=!0;try{const t=await i.validate({runtimeAdapter:e.agentType,workDir:e.workDir,modelId:e.modelId??null,sessionMode:"standard"});if(!t.ok)throw new n(t.errorCode,t.errorCode);return{ready:!0,agentType:e.agentType,modelId:e.modelId??null,scope:"hello_reply"}}finally{r=!1}}}}export{u as createAgentReadinessHandlers};
@@ -2,7 +2,7 @@ import type { DetectedAgent } from '../agents/detect.js';
2
2
  import type { DaemonStatus } from '../commands/daemon.js';
3
3
  import type { JsonValue } from './json.js';
4
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"];
5
+ export declare const DAEMON_CONTROL_CAPABILITIES: readonly ["protocol.info", "daemon.status", "agent.list", "agent.discover", "agent.check", "agent.auth.completed", "log.read", "upgrade.start", "upgrade.prepare-external", "upgrade.cancel-external"];
6
6
  export declare function createDaemonControlHandlers(input: {
7
7
  daemonVersion: string;
8
8
  protocolVersion: number;
@@ -1 +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};
1
+ import o from"node:fs";import{getAcpLocalMetrics as d}from"../agents/acp/metrics.js";const g=["protocol.info","daemon.status","agent.list","agent.discover","agent.check","agent.auth.completed","log.read","upgrade.start","upgrade.prepare-external","upgrade.cancel-external"],p=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:[...g]}},"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:p,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{g as DAEMON_CONTROL_CAPABILITIES,_ as createDaemonControlHandlers,y as readDaemonLogPage};
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{registerScheduleCommand as fe}from"./commands/schedule.js";import{resolvePersonalScheduleContext as ve}from"./schedules/personal-context.js";import{executeServerSchedule as Z,deliverServerSchedule as Ie}from"./schedules/server-client.js";import{Command as Se}from"commander";import R from"chalk";import ye from"node:os";import j from"node:fs";import{loadConfig as m,saveConfig as we,getConfigPath as Ce,getShennianDir as be,resolveShennianPath as H,normalizeAutoUpgradePolicy as ke}from"./config/index.js";import{CliRelayClient as Re}from"./relay/client.js";import{collectKnownLogSecrets as X,installCliLogRedaction as Pe,sanitizeCliLogValue as Ee}from"./log-redaction.js";import{registerPairCommand as Ae,runSmartStart as Te}from"./commands/pair.js";import{registerLogoutCommand as Ue}from"./commands/logout.js";import{clearDaemonPidIfOwner as ee,createDaemonInstanceId as De,clearDaemonLauncher as oe,findRunningDaemonProcessIds as Me,isRemoteAccessDisabled as ne,prepareRemoteAccessPause as _e,stopPreparedRemoteAccessService as xe,isIsolatedDaemonE2eInstance as $e,getDaemonStatus as Ne,registerDaemonCommand as Oe,writeDaemonPid as re,writeDaemonLauncher as te}from"./commands/daemon.js";import{registerRuntimeCommand as Ve}from"./commands/runtime.js";import{registerAgentCommand as Le}from"./commands/agent.js";import{registerManagerCommand as je}from"./commands/manager.js";import{handleUpgradeStart as He,registerUpgradeCommand as Be}from"./commands/upgrade.js";import{registerToolsCommand as Fe}from"./commands/tools.js";import{registerIntegrationCommand as Ge}from"./commands/integration.js";import{registerRoomCommand as qe}from"./commands/room.js";import{SessionManager as ze}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as Je}from"./session/handlers/project-fs.js";import{SERVERS as B,regionToUrl as Ke,urlToRegion as We}from"./region.js";import{clearUpgradeAttempt as Ye,getCurrentVersion as ae,handleStartupCrashCheck as Qe,readUpgradeAttempt as Ze}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Xe}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as eo}from"./upgrade/updater-bootstrap.js";import{launchUpdater as oo,markUpdaterHealthy as no}from"./upgrade/updater-launcher.js";import{detectAgents as ro}from"./agents/detect.js";import{augmentProcessPath as ie}from"./env-path.js";import{acquireDaemonInstanceLock as to}from"./commands/daemon-instance-lock.js";ie();const F=ae();import{getCachedAgentInfos as ao,resolveAgentInfos as io}from"./agents/model-registry.js";import{initCliLogReporter as so,reportLog as P,reportUpgradeAggregateMetric as co}from"./log-reporter.js";import{isNativeFusionEnabled as lo}from"./native-fusion/config.js";import{NativeSessionFusionService as mo}from"./native-fusion/service.js";import{startDaemonLogRetention as po}from"./daemon-log.js";import{startDaemonIpcServer as se}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as go,resolveManagedRoomIpcPaths as uo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as ho}from"./daemon-ipc/control.js";import{DAEMON_IPC_VERSION as fo}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as vo}from"./room/session-registry.js";import{HttpRoomBindingClient as Io}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as So}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as yo}from"./room/host-session-observations.js";import{RoomMirrorSyncService as wo}from"./room/mirror-sync-service.js";import{NativeSourceDeletionStore as Co}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as bo,isCurrentRegistration as ko}from"./room/registration-authority.js";import{RoomActivationService as Ro,resolveManagedAgentPolicy as Po}from"./room/activation-service.js";import{RoomActivationAudit as Eo}from"./room/activation-audit.js";import{RoomAttachmentCache as Ao}from"./room/attachment-cache.js";import{RoomAttachmentUploader as To}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Uo}from"./room/managed-context.js";import{createManagedRoomExecutor as Do}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as ce,SESSION_SYNC_ADVERTISED_CAPABILITIES as de}from"@shennian/wire";import{getDaemonInstallationId as Mo}from"./session/store.js";import{synchronizeMachinePersonalAuthority as _o}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as le,ensurePersonalDataEpoch as xo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as $o,DeletionSyncTerminalError as No}from"./session/deletion-sync-state.js";const me=be(),Oo=H("daemon.pid"),Vo=H("daemon.instance.lock");function Lo(){try{const a=j.readFileSync(Oo,"utf-8").trim();if(a.startsWith("{")){const p=JSON.parse(a),c=Number(p.pid);return Number.isInteger(c)&&c>0?c:null}const s=parseInt(a,10);return Number.isInteger(s)&&s>0?s:null}catch{return null}}function jo(a){return a.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 Ho(a){const s=[`trigger=${a.trigger??"unknown"}`,`phase=${a.phase}`,`code=${a.code??"unknown"}`,`attempt=${a.reconnectAttempt}`];return a.reason&&s.push(`reason=${a.reason}`),a.error&&s.push(`error=${a.error}`),s.join(" ")}const i=new Se;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(F),i.enablePositionalOptions(),Ge(i),qe(i),fe(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",ye.hostname()).action(async a=>{const s=m(),p=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await Te(p,a.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 a=>{const s=H("env.json");try{const e=JSON.parse(j.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);ie()}catch{}Pe(X(process.env,m()));const p=po();ne()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(p),process.exit(0));const c=De(),S=to({lockPath:Vo,instanceId:c});S.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(S.ownerPid?` (PID ${S.ownerPid})`:"")+", skipping duplicate start"),clearInterval(p),process.exit(0));try{const e=Lo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),S.release(),process.exit(0)}catch{}}catch{}const q=$e(process.env,me)?[]:Me(process.pid);q.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${q[0]}), skipping duplicate start`),S.release(),process.exit(0)),re(process.pid,c,{version:F}),te(process.pid,void 0,c),process.on("exit",()=>{ee(process.pid,c),oe(c)}),process.on("exit",S.release);let $=null;try{$=eo()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const E=await Qe();if(E){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{oo({fromVersion:F,targetVersion:E.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:E.jobId},{readiness:$}),console.error(`[upgrade] New version crashed ${E.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{xo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const d=m(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??d.serverUrl??B.cn.url,h=ae(),y=await ro({serverUrl:g,authToken:d.machineToken??d.accessToken}),w=y.map(e=>e.type),N=ao(y),u=new vo,A=new yo,C=new Uo(u),v=new Io(g),ge=new Ao({actionClient:v}),ue=new To({actionClient:v});let I=null,t;const z=Do({authority:C,actionClient:v,attachmentCache:ge,attachmentUploader:ue,observations:A,getMachineCredentials:()=>({machineToken:m().machineToken}),managerExecutor:e=>t.executeManagedManagerAction(e),scheduleExecutor:(e,n,o)=>Z({serverUrl:g,machineToken:m().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const r=u.getByRegistryKey(e)?.binding;r&&u.setBinding({registryKey:e,bindingId:r.bindingId,roomId:r.roomId,agentIdentityId:r.agentIdentityId,agentMembershipId:r.agentMembershipId,status:"paused",activationMode:"paused"}),C.revokeSession(e),t?.discardPendingManagedRoomMessages(n)}});let O=null,V=null,L=null;const b=await se({paths:go(),handlers:ho({daemonVersion:h,protocolVersion:fo,getStatus:()=>Ne(),getAgents:()=>y,completeAgentAuthentication:e=>t.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!O)throw new Error("upgrade_unavailable");return O(e)},prepareExternalUpgrade:e=>{if(!V)throw new Error("external_upgrade_unavailable");return V(e)},cancelExternalUpgrade:()=>{if(!L)throw new Error("external_upgrade_unavailable");return L()}})});let k;try{k=await se({paths:uo(),handlers:{"managed-room.execute":e=>z(e),"schedule.personal":async e=>{const n=e,o=ve(n?.context);if(u.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await Z({serverUrl:g,machineToken:m().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let J=!1;const T=()=>{J||(J=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",T),process.once("SIGTERM",T),!d.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",T),process.removeListener("SIGTERM",T);const K=`${jo(g)}/relay/machine`,U=d.machineToken;if(!U)return;try{await _o({serverUrl:g,machineToken:U,machineId:d.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),k.close()]),process.exit(1)}d.machineId&&so(g,d.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${K}... (v${h}) agents: ${w.join(",")}`),P({level:"info",wsEvent:"daemon.start",metadata:{version:h,agents:w}});let D=null,M=null,_=0,f=Ze();const W=ke(d.autoUpgrade),l=new Re({serverUrl:K,machineToken:U,cliVersion:h,agentList:w,autoUpgradePolicy:W,sessionSyncProtocolVersion:ce,sessionSyncCapabilities:de,daemonInstallationId:Mo(),dataEpoch:le,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),P({level:"info",wsEvent:"daemon.connected"}),N.some(o=>o.models.length>0)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:N}}),io(y,{serverUrl:d.serverUrl??g,authToken:d.machineToken??d.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(N)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:o}})}).catch(()=>{}),f&&l.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:f.from,to:f.to,...f.jobId?{jobId:f.jobId}:{},activeRunCount:t.getPendingRecoveryCount()}}),x.start(),M?.start();const e=++_,n=async()=>{try{if(await t.synchronizeSessionDeletions(),await t.recoverInterruptedRuns(),f){const o=f;f=null,o.jobId&&no(o.jobId,o.to),Ye(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),co({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),l.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"&&l.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===_&&l.getState()==="connected"&&D?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===_&&l.getState()==="connected"){if(o instanceof No)return;const r=o instanceof $o?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},r).unref?.()}}};n()},onDisconnected:e=>{_+=1,t?.revokeProjectFsHandles(),M?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${Ho(e)}`),P({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||t?.revokeProjectFsHandles()},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),P({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),t.handleReq(e)}});D=lo()?new mo(l):null,M=new wo({registry:u,observations:A,actionClient:v,getMachineCredentials:()=>({machineToken:m().machineToken}),isSessionDeleted:e=>new Co().hasSession(e),onMessages:async(e,n,o,r)=>{await I?.processMessages(e,n,o,r)},onBindingPaused:e=>{C.revokeSession(e.registryKey),e.lifecycleState==="closed"?t?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")}):t?.discardPendingManagedRoomMessages(e.nianSessionId)}});const x=new Xe({currentVersion:h,machineId:d.machineId??"unknown-machine",serverUrl:d.serverUrl??g,getIdleState:()=>t.getUpgradeIdleState(),sessionSyncProtocolVersion:ce,dataEpoch:le,advertisedCapabilities:de,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(l,"auto-upgrade",e,{currentVersion:h,prepareForUpgrade:r=>t.prepareForUpgrade(r),cancelUpgradePreparation:()=>t.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:n?void 0:()=>t.getUpgradeIdleState().idle})}});x.setPolicy(W),t=new ze(l,D,h,x,new So({registry:u,observations:A,bindingClient:v,getMachineCredentials:()=>{const e=m();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>_e(),isDisabled:()=>ne(),shutdownAfterResponse:()=>{setTimeout(()=>{xe(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>C.revokeToken(e),z,null,Je(U)),O=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 He(l,`local-upgrade-${Date.now()}`,o,{currentVersion:h,confirmedMajor:r,prepareForUpgrade:he=>t.prepareForUpgrade(he),cancelUpgradePreparation:()=>t.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,...t.prepareForUpgrade(r)}},L=()=>(t.cancelUpgradePreparation(),{cancelled:!0}),I=new Ro({registry:u,observations:A,enqueueConversationBatch:e=>t.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=m().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const r=await v.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});if(!ko(u,e)||!bo(e,r))throw new Error("managed_room_binding_unavailable");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,o)=>{const r=m().machineToken;if(!r)throw new Error("machine_not_authorized");return C.issue({registryKey:e,machineCredential:r,activationCauseMessageId:n,allowedActions:o})},audit:new Eo,resolveManagedAgentPolicy:e=>Po({...e,runtime:y.find(n=>n.type===e.registration.runtimeAdapter)})}),t.scheduleHandler=e=>Ie({serverUrl:g,machineToken:m().machineToken,req:e,client:l,queue:t.getScheduleQueue(),registry:u}),t.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(u.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await v.status({machineToken:m().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},t.setManagedRoomTurnPreparer(e=>I.prepareConversationTurn(e)),t.setManagedAgentDirectTurnPreparer((e,n)=>I.prepareDirectConversationTurn(e,n)),t.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?I.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[]}):e.source==="manager_event"?I.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:I.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),j.mkdirSync(me,{recursive:!0}),re(process.pid,c,{version:h}),te(process.pid,void 0,c),l.connect(),process.stdin.resume();let Y=!1;const Q=async()=>{if(!Y){Y=!0,console.log(R.gray(`
3
- Disconnecting...`)),P({level:"info",wsEvent:"daemon.stop"}),clearInterval(p);try{await t.cleanup(),x.stop(),D?.stop(),M?.stop(),l.disconnect(),await Promise.allSettled([b.close(),k.close()]),ee(process.pid,c),oe(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{Q()}),process.on("SIGTERM",()=>{Q()})}),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 pe=i.command("config").description("View or modify configuration");pe.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${Ce()}`);const a=m(),s=Ee(a,X(process.env,a));if(a.serverUrl){const p=We(a.serverUrl);console.log(`Region: ${p} (${B[p].label})`)}console.log(JSON.stringify(s,null,2))}),pe.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((a,s)=>{const p=m();if(a==="server"){s!=="cn"&&s!=="global"&&(console.error(R.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=s;p.serverUrl=Ke(c),we(p),console.log(R.green(`\u2713 Server set to ${B[c].label}`)),console.log(R.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(R.red(`\u2717 Unknown config key: ${a}. Supported: server`)),process.exit(1)}),Ae(i),Ue(i),Oe(i),Ve(i),Le(i),je(i),Be(i),Fe(i),i.parse();
1
+ import{registerScheduleCommand as ve}from"./commands/schedule.js";import{resolvePersonalScheduleContext as Ie}from"./schedules/personal-context.js";import{executeServerSchedule as Z,deliverServerSchedule as Se}from"./schedules/server-client.js";import{Command as ye}from"commander";import P from"chalk";import we from"node:os";import j from"node:fs";import{loadConfig as m,saveConfig as Ce,getConfigPath as be,getShennianDir as ke,resolveShennianPath as H,normalizeAutoUpgradePolicy as Re}from"./config/index.js";import{CliRelayClient as Pe}from"./relay/client.js";import{collectKnownLogSecrets as X,installCliLogRedaction as Ae,sanitizeCliLogValue as Ee}from"./log-redaction.js";import{registerPairCommand as Te,runSmartStart as De}from"./commands/pair.js";import{registerLogoutCommand as Ue}from"./commands/logout.js";import{clearDaemonPidIfOwner as ee,createDaemonInstanceId as Me,clearDaemonLauncher as oe,findRunningDaemonProcessIds as _e,isRemoteAccessDisabled as ne,prepareRemoteAccessPause as xe,stopPreparedRemoteAccessService as $e,isIsolatedDaemonE2eInstance as Ne,getDaemonStatus as te,registerDaemonCommand as Oe,writeDaemonPid as re,writeDaemonLauncher as ae}from"./commands/daemon.js";import{registerRuntimeCommand as Ve}from"./commands/runtime.js";import{registerAgentCommand as Le}from"./commands/agent.js";import{registerManagerCommand as je}from"./commands/manager.js";import{handleUpgradeStart as He,registerUpgradeCommand as Be}from"./commands/upgrade.js";import{registerToolsCommand as Fe}from"./commands/tools.js";import{registerIntegrationCommand as Ge}from"./commands/integration.js";import{registerRoomCommand as qe}from"./commands/room.js";import{SessionManager as ze}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as Je}from"./session/handlers/project-fs.js";import{SERVERS as B,regionToUrl as Ke,urlToRegion as We}from"./region.js";import{clearUpgradeAttempt as Ye,getCurrentVersion as ie,handleStartupCrashCheck as Qe,readUpgradeAttempt as Ze}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Xe}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as eo}from"./upgrade/updater-bootstrap.js";import{isDesktopDistribution as oo}from"./runtime-distribution.js";import{launchUpdater as no,markUpdaterHealthy as to}from"./upgrade/updater-launcher.js";import{detectAgents as ro}from"./agents/detect.js";import{augmentProcessPath as se}from"./env-path.js";import{acquireDaemonInstanceLock as ao}from"./commands/daemon-instance-lock.js";se();const F=ie();import{getCachedAgentInfos as io,resolveAgentInfos as so}from"./agents/model-registry.js";import{initCliLogReporter as co,reportLog as A,reportUpgradeAggregateMetric as lo}from"./log-reporter.js";import{isNativeFusionEnabled as mo}from"./native-fusion/config.js";import{NativeSessionFusionService as po}from"./native-fusion/service.js";import{startDaemonLogRetention as go}from"./daemon-log.js";import{startDaemonIpcServer as ce}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as uo,resolveManagedRoomIpcPaths as fo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as ho}from"./daemon-ipc/control.js";import{createAgentReadinessHandlers as vo}from"./daemon-ipc/agent-readiness.js";import{scanMachineAgents as Io}from"./session/handlers/agents.js";import{validateAgentWorkspaceConfiguration as So}from"./session/handlers/agent-workspace-validation.js";import{DAEMON_IPC_VERSION as yo}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as wo}from"./room/session-registry.js";import{HttpRoomBindingClient as Co}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as bo}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as ko}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Ro}from"./room/mirror-sync-service.js";import{NativeSourceDeletionStore as Po}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as Ao,isCurrentRegistration as Eo}from"./room/registration-authority.js";import{RoomActivationService as To,resolveManagedAgentPolicy as Do}from"./room/activation-service.js";import{RoomActivationAudit as Uo}from"./room/activation-audit.js";import{RoomAttachmentCache as Mo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as _o}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as xo}from"./room/managed-context.js";import{createManagedRoomExecutor as $o}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as de,SESSION_SYNC_ADVERTISED_CAPABILITIES as le}from"@shennian/wire";import{getDaemonInstallationId as No}from"./session/store.js";import{synchronizeMachinePersonalAuthority as Oo}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as me,ensurePersonalDataEpoch as Vo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Lo,DeletionSyncTerminalError as jo}from"./session/deletion-sync-state.js";const pe=ke(),Ho=H("daemon.pid"),Bo=H("daemon.instance.lock");function Fo(){try{const a=j.readFileSync(Ho,"utf-8").trim();if(a.startsWith("{")){const p=JSON.parse(a),c=Number(p.pid);return Number.isInteger(c)&&c>0?c:null}const s=parseInt(a,10);return Number.isInteger(s)&&s>0?s:null}catch{return null}}function Go(a){return a.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 qo(a){const s=[`trigger=${a.trigger??"unknown"}`,`phase=${a.phase}`,`code=${a.code??"unknown"}`,`attempt=${a.reconnectAttempt}`];return a.reason&&s.push(`reason=${a.reason}`),a.error&&s.push(`error=${a.error}`),s.join(" ")}const i=new ye;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(F),i.enablePositionalOptions(),Ge(i),qe(i),ve(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",we.hostname()).action(async a=>{const s=m(),p=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await De(p,a.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 a=>{const s=H("env.json");try{const e=JSON.parse(j.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);se()}catch{}Ae(X(process.env,m()));const p=go();ne()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(p),process.exit(0));const c=Me(),y=ao({lockPath:Bo,instanceId:c});y.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(y.ownerPid?` (PID ${y.ownerPid})`:"")+", skipping duplicate start"),clearInterval(p),process.exit(0));try{const e=Fo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),y.release(),process.exit(0)}catch{}}catch{}const q=Ne(process.env,pe)?[]:_e(process.pid);q.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${q[0]}), skipping duplicate start`),y.release(),process.exit(0)),re(process.pid,c,{version:F}),ae(process.pid,void 0,c),process.on("exit",()=>{ee(process.pid,c),oe(c)}),process.on("exit",y.release);let $=null;try{oo()||($=eo())}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const E=await Qe();if(E){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{no({fromVersion:F,targetVersion:E.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:E.jobId},{readiness:$}),console.error(`[upgrade] New version crashed ${E.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{Vo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const l=m(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??l.serverUrl??B.cn.url,f=ie(),v=await ro({serverUrl:g,authToken:l.machineToken??l.accessToken}),w=v.map(e=>e.type),N=io(v),u=new wo,T=new ko,C=new xo(u),I=new Co(g),ue=new Mo({actionClient:I}),fe=new _o({actionClient:I});let S=null,r;const z=$o({authority:C,actionClient:I,attachmentCache:ue,attachmentUploader:fe,observations:T,getMachineCredentials:()=>({machineToken:m().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),scheduleExecutor:(e,n,o)=>Z({serverUrl:g,machineToken:m().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=u.getByRegistryKey(e)?.binding;t&&u.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),C.revokeSession(e),r?.discardPendingManagedRoomMessages(n)}});let O=null,V=null,L=null;const b=await ce({paths:uo(),handlers:{...ho({daemonVersion:f,protocolVersion:yo,getStatus:()=>te(),getAgents:()=>v,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!O)throw new Error("upgrade_unavailable");return O(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()}}),...vo({getStatus:()=>te(),discover:async()=>{if(!r)throw new Error("daemon_starting");const e=await Io(!0);return v.splice(0,v.length,...e.detectedAgents),d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:e.agentList,agents:e.agents}}),e.agents.map(n=>{const o=e.detectedAgents.find(t=>t.type===n.type);return{...n,runtimePlatform:o?.runtimePlatform,runtimeIsWsl:o?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return So(e,{ignoreNativeSession:(n,o)=>R?.ignoreNativeSource(n,o)})}})}});let k;try{k=await ce({paths:fo(),handlers:{"managed-room.execute":e=>z(e),"schedule.personal":async e=>{const n=e,o=Ie(n?.context);if(u.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await Z({serverUrl:g,machineToken:m().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let J=!1;const D=()=>{J||(J=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",D),process.once("SIGTERM",D),!l.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",D),process.removeListener("SIGTERM",D);const K=`${Go(g)}/relay/machine`,U=l.machineToken;if(!U)return;try{await Oo({serverUrl:g,machineToken:U,machineId:l.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),k.close()]),process.exit(1)}l.machineId&&co(g,l.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${K}... (v${f}) agents: ${w.join(",")}`),A({level:"info",wsEvent:"daemon.start",metadata:{version:f,agents:w}});let R=null,M=null,_=0,h=Ze();const W=Re(l.autoUpgrade),d=new Pe({serverUrl:K,machineToken:U,cliVersion:f,agentList:w,autoUpgradePolicy:W,sessionSyncProtocolVersion:de,sessionSyncCapabilities:le,daemonInstallationId:No(),dataEpoch:me,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),A({level:"info",wsEvent:"daemon.connected"}),N.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:N}}),so(v,{serverUrl:l.serverUrl??g,authToken:l.machineToken??l.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(N)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:o}})}).catch(()=>{}),h&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:h.from,to:h.to,...h.jobId?{jobId:h.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),x.start(),M?.start();const e=++_,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),h){const o=h;h=null,o.jobId&&to(o.jobId,o.to),Ye(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),lo({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===_&&d.getState()==="connected"&&R?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===_&&d.getState()==="connected"){if(o instanceof jo)return;const t=o instanceof Lo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{_+=1,r?.revokeProjectFsHandles(),M?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${qo(e)}`),A({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||r?.revokeProjectFsHandles()},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),A({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),r.handleReq(e)}});R=mo()?new po(d):null,M=new Ro({registry:u,observations:T,actionClient:I,getMachineCredentials:()=>({machineToken:m().machineToken}),isSessionDeleted:e=>new Po().hasSession(e),onMessages:async(e,n,o,t)=>{await S?.processMessages(e,n,o,t)},onBindingPaused:e=>{C.revokeSession(e.registryKey),e.lifecycleState==="closed"?r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")}):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const x=new Xe({currentVersion:f,machineId:l.machineId??"unknown-machine",serverUrl:l.serverUrl??g,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:de,dataEpoch:me,advertisedCapabilities:le,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(d,"auto-upgrade",e,{currentVersion:f,prepareForUpgrade:t=>r.prepareForUpgrade(t),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:n?void 0:()=>r.getUpgradeIdleState().idle})}});x.setPolicy(W),r=new ze(d,R,f,x,new bo({registry:u,observations:T,bindingClient:I,getMachineCredentials:()=>{const e=m();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>xe(),isDisabled:()=>ne(),shutdownAfterResponse:()=>{setTimeout(()=>{$e(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>C.revokeToken(e),z,null,Je(U)),O=async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof n.targetVersion=="string"?n.targetVersion:void 0,t=n.confirmedMajor===!0;return await He(d,`local-upgrade-${Date.now()}`,o,{currentVersion:f,confirmedMajor:t,prepareForUpgrade:he=>r.prepareForUpgrade(he),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},V=e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof n.targetVersion=="string"?n.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...r.prepareForUpgrade(t)}},L=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),S=new To({registry:u,observations:T,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=m().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const t=await I.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});if(!Eo(u,e)||!Ao(e,t))throw new Error("managed_room_binding_unavailable");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,n,o)=>{const t=m().machineToken;if(!t)throw new Error("machine_not_authorized");return C.issue({registryKey:e,machineCredential:t,activationCauseMessageId:n,allowedActions:o})},audit:new Uo,resolveManagedAgentPolicy:e=>Do({...e,runtime:v.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>Se({serverUrl:g,machineToken:m().machineToken,req:e,client:d,queue:r.getScheduleQueue(),registry:u}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(u.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await I.status({machineToken:m().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>S.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,n)=>S.prepareDirectConversationTurn(e,n)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?S.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[]}):e.source==="manager_event"?S.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:S.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),j.mkdirSync(pe,{recursive:!0}),re(process.pid,c,{version:f}),ae(process.pid,void 0,c),d.connect(),process.stdin.resume();let Y=!1;const Q=async()=>{if(!Y){Y=!0,console.log(P.gray(`
3
+ Disconnecting...`)),A({level:"info",wsEvent:"daemon.stop"}),clearInterval(p);try{await r.cleanup(),x.stop(),R?.stop(),M?.stop(),d.disconnect(),await Promise.allSettled([b.close(),k.close()]),ee(process.pid,c),oe(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{Q()}),process.on("SIGTERM",()=>{Q()})}),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: ${be()}`);const a=m(),s=Ee(a,X(process.env,a));if(a.serverUrl){const p=We(a.serverUrl);console.log(`Region: ${p} (${B[p].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((a,s)=>{const p=m();if(a==="server"){s!=="cn"&&s!=="global"&&(console.error(P.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=s;p.serverUrl=Ke(c),Ce(p),console.log(P.green(`\u2713 Server set to ${B[c].label}`)),console.log(P.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(P.red(`\u2717 Unknown config key: ${a}. Supported: server`)),process.exit(1)}),Te(i),Ue(i),Oe(i),Ve(i),Le(i),je(i),Be(i),Fe(i),i.parse();
@@ -1,14 +1,18 @@
1
1
  import { type SessionDeliveryState } from '@shennian/wire';
2
2
  import type { CanonicalSessionStore } from '../session/canonical-store.js';
3
3
  import { listSessionRecords, recordSession } from '../session/store.js';
4
- import type { ManagedSuppressionWindow, ParsedNativeEvent, PendingManagedClaim } from './types.js';
5
4
  import { type LocalToolDetail } from '../session/tool-detail-store.js';
6
5
  import { NativeSourceDeletionStore } from './native-source-deletions.js';
6
+ import type { ManagedSuppressionWindow, ParsedNativeEvent, PendingManagedClaim } from './types.js';
7
7
  declare const SCHEMA_VERSION: 1;
8
8
  export declare class MissingUserBoundaryError extends Error {
9
9
  readonly code = "missing_user_boundary";
10
10
  constructor(sourceSessionKey: string);
11
11
  }
12
+ export declare class InternalNativeSourceError extends Error {
13
+ readonly code = "internal_native_source";
14
+ constructor();
15
+ }
12
16
  export declare class NativeSourceDeletedError extends Error {
13
17
  readonly code = "native_source_deleted";
14
18
  constructor(sourceSessionKey: string);
@@ -64,6 +68,7 @@ export declare class NativeFusionLocalRepository {
64
68
  removeManagedSuppression(agentType: string, sourceSessionKey: string): void;
65
69
  listSourceBindings(): SourceBinding[];
66
70
  purgeSession(sessionId: string): void;
71
+ isSourceInternal(agentType: ParsedNativeEvent['agentType'], sourceSessionKey: string): boolean;
67
72
  isSourceDeleted(agentType: ParsedNativeEvent['agentType'], sourceSessionKey: string): boolean;
68
73
  observeNativeTitle(agentType: ParsedNativeEvent['agentType'], sourceSessionKey: string, titleValue: string): boolean;
69
74
  private recordIndex;
@@ -1,2 +1,2 @@
1
- import y from"node:crypto";import d from"node:fs";import S from"node:path";import{isToolPayload as B,sessionPreviewFromPayload as L}from"@shennian/wire";import{resolvePersonalSyncV1Path as P}from"../personal-sync/paths.js";import{getCanonicalSessionStore as H,getDaemonInstallationId as U,listSessionRecords as x,recordSession as w}from"../session/store.js";import{readToolDetail as V,writeToolDetail as q}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as J}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as v}from"../personal-sync/metrics.js";const E=1;class W extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class z extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class G extends Error{sourceEventKey;code="native_event_identity_conflict";constructor(e){super(`native event identity conflict: ${e}`),this.sourceEventKey=e,this.name="NativeEventIdentityConflictError"}}class ue{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}updateMessageDeliveryState(e,s,o){if(!this.canonicalStore().updateMessageDeliveryState(e,s,o))return;const i=(this.options.listLocalSessions??x)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(Q(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new z(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=Z(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=d.existsSync(a)?D(a):null;if(r&&r.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(r&&r.eventFingerprint!==c)throw new G(e.sourceEventKey);if(!i&&!r&&e.role!=="user")throw new W(e.sourceSessionKey);const u=e.claimSessionId??null,l=i?.sessionId??r?.sessionId??u??X(s,e.agentType,e.sourceSessionKey);if(u&&i&&i.sessionId!==u)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const p=this.canonicalStore(),I=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",g=r?.canonicalMessageId??(I?null:e.claimCanonicalMessageId??Y(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let f=p.getManifest(l)?.bodyRevision??0,T=!!r;const M=!I&&!!e.claimCanonicalMessageId;if(M){if(!g)throw new Error("managed echo claim requires a canonical message");const h=p.getMessageState(l,g);if(!h||h.tombstoned)throw new Error(`managed echo claim target is not durable: ${g}`);h.deliveryState==="queued"&&h.message&&(f=p.upsert(l,{...h.message,ts:e.ts},"executing").bodyRevision)}if(!r&&!M&&!I){if(!g)throw new Error("native event requires a canonical message");const h=e.canonicalMessageKey?p.getMessageState(l,g):null,F=e.canonicalMutation==="upsert"||!!h,$=F?h??p.getMessageState(l,g):null,_={id:g,sessionId:l,role:e.role,ts:$?.message?.ts??e.ts,payload:ee($?.message?.payload,e.payload)},C=F?p.upsert(l,_):p.append(l,_);if(f=C.bodyRevision,T=C.duplicate,e.localToolDetail){const N=this.options.writeLocalToolDetail??q,j=this.options.readLocalToolDetail??V,R=se(j(l,e.localToolDetail.toolCallId),e.localToolDetail);N({sessionId:l,...R,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(f=p.completeStartedUserMessages(l,e.ts).at(-1)?.bodyRevision??f),(!r||e.terminal)&&!I&&this.recordIndex(e,l,g,f);const K=new Date().toISOString();return r||m(a,{schemaVersion:E,daemonInstallationId:s,sessionId:l,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:g,canonicalRevision:f,cursor:e.cursor??null,mappedBy:I?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:K}),o.sources[n]={schemaVersion:E,daemonInstallationId:s,sessionId:l,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:r?i?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:r?i?.lastCursor??r.cursor:e.cursor??i?.lastCursor??null,bodyRevision:f,createdAt:i?.createdAt??K,updatedAt:K},m(this.sourcesFile(),o),!r&&e.cursor&&v("sourceCursorAdvanced"),!r&&!M&&!I&&!T&&v("canonicalCommits"),{sessionId:l,messageId:g,bodyRevision:f,duplicate:T,claimed:M,suppressed:I}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??x,s=this.options.recordLocalSession??w,o=this.canonicalStore();let n=0;for(const i of e()){if(i.localReady)continue;const a=o.getManifest(i.sessionId)?.firstAvailableSeq;if(a==null)continue;const r=o.readPage(i.sessionId,{limit:1,beforeSequence:a+1}).entries[0];!r||r.sequence!==a||r.message.role!=="user"||(s({sessionId:i.sessionId,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId,status:i.status,lastActivityAt:i.lastActivityAt,lastMessagePreview:i.lastMessagePreview,localReady:!0,firstUserMessageId:r.message.id,firstUserBodyRevision:r.revision}),n+=1)}return n}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){A(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,m(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&m(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],m(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(O(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),m(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],m(this.managedEchoStateFile(),o))}listSourceBindings(){return Object.values(this.readSources().sources).filter(e=>!this.isSourceDeleted(e.agentType,e.sourceSessionKey))}purgeSession(e){if(!e)throw new Error("sessionId is required");const s=this.readSources();let o=!1;for(const[a,r]of Object.entries(s.sources))r.sessionId===e&&(delete s.sources[a],o=!0);o&&m(this.sourcesFile(),s);const n=this.readManagedEchoState();let i=!1;for(const[a,r]of Object.entries(n.claims))r.sessionId===e&&(delete n.claims[a],i=!0);for(const[a,r]of Object.entries(n.suppressions))r.sessionId===e&&(delete n.suppressions[a],i=!0);i&&m(this.managedEchoStateFile(),n);const c=S.join(this.rootDir(),"ledger");if(d.existsSync(c))for(const a of d.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=S.join(c,a.name),u=D(r);if(u.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");u.sessionId===e&&d.unlinkSync(r)}}isSourceDeleted(e,s){return(this.options.deletionStore??new J({rootDir:S.basename(this.rootDir())==="native-fusion"?S.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const n=o.trim().slice(0,120);if(!n)return!1;const i=this.readSources().sources[b(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??x)().find(u=>u.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=y.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??w)({sessionId:i.sessionId,agentType:i.agentType,workDir:c?.workDir??"",agentSessionId:s,title:n,titleSource:"agent_native",titleOriginEventId:`agent-native:${a}`}),!0}recordIndex(e,s,o,n){const i=this.options.recordLocalSession??w,c=e.terminal?`terminal:${y.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?k(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{},u=o&&(a||B(e.payload))?new Date(e.ts).toISOString():void 0;i({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?e.terminalState==="failed"||e.terminalState==="aborted"?"failed":"completed":"active",attentionId:c,lastActivityAt:new Date(e.ts).toISOString(),lastVisibleEventAt:u,...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?k(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!d.existsSync(e))return{schemaVersion:E,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==E||s.daemonInstallationId!==this.installationId()||!s.sources)throw new Error("invalid native source binding store");for(const o of Object.values(s.sources))if(o.daemonInstallationId!==this.installationId())throw new Error("native source binding belongs to another daemon installation");return s}installationId(){return this.options.daemonInstallationId??U()}canonicalStore(){return this.options.canonicalStore??H()}rootDir(){return this.options.rootDir??P("native-fusion")}sourcesFile(){return S.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return S.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!d.existsSync(e))return{schemaVersion:E,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==E||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))A(o);for(const o of Object.values(s.suppressions))O(o);return s}ledgerFile(e,s){const o=y.createHash("sha256").update(`${e}\0${s}`).digest("hex");return S.join(this.rootDir(),"ledger",`${o}.json`)}}function Q(t){if(!t.agentType||!t.sourceSessionKey||!t.sourceEventKey||!t.payload)throw new Error("invalid native event");if(!Number.isFinite(t.ts))throw new Error("invalid native event timestamp")}function b(t,e){return y.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function X(t,e,s){return`native-session-${y.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Y(t,e,s){return`native-${y.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function Z(t){return y.createHash("sha256").update(JSON.stringify({agentType:t.agentType,sourceSessionKey:t.sourceSessionKey,sourceEventKey:t.sourceEventKey,role:t.role,ts:t.ts,payload:t.payload,canonicalMessageKey:t.canonicalMessageKey??null,canonicalMutation:t.canonicalMutation??"append"})).digest("hex")}function A(t){if(!t.sessionId||!t.canonicalMessageId||!t.textHash||!Number.isFinite(t.createdAt)||!["codex","claude","opencode"].includes(t.sourceAgentType)||!["suppress_following","import_following"].includes(t.managedEchoPolicy))throw new Error("invalid managed echo claim")}function O(t){if(!t.sessionId||!t.sourceSessionKey||!t.claimSourceEventKey||!["codex","claude","opencode"].includes(t.agentType)||!Number.isFinite(t.startedAt)||!Number.isFinite(t.endsAt)||t.endsAt<t.startedAt)throw new Error("invalid managed echo suppression")}function ee(t,e){if(!t)return e;try{const s=JSON.parse(t),o=JSON.parse(e);if(s.type!=="tool"||o.type!=="tool"||s.toolCallId!==o.toolCallId)return e;const n=typeof o.name=="string"?o.name:"",i=/(?:_output|_result)$/.test(n);return JSON.stringify({...s,...o,...i&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function se(t,e){if(!t||t.toolCallId!==e.toolCallId)return e;const s=/(?:_output|_result)$/.test(e.name);return{...e,name:s?t.name:e.name,...e.args===void 0&&t.args!==void 0?{args:t.args}:{},...e.result===void 0&&t.result!==void 0?{result:t.result}:{}}}function k(t){return L(t,120)}function m(t,e){const s=S.dirname(t);d.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${y.randomBytes(6).toString("hex")}`;let n;try{n=d.openSync(o,"wx",384),d.writeFileSync(n,`${JSON.stringify(e,null,2)}
2
- `,"utf8"),d.fsyncSync(n),d.closeSync(n),n=void 0,d.renameSync(o,t)}finally{n!==void 0&&d.closeSync(n);try{d.unlinkSync(o)}catch{}}}function D(t){return JSON.parse(d.readFileSync(t,"utf8"))}export{W as MissingUserBoundaryError,G as NativeEventIdentityConflictError,ue as NativeFusionLocalRepository,z as NativeSourceDeletedError};
1
+ import{isToolPayload as B,sessionPreviewFromPayload as L}from"@shennian/wire";import y from"node:crypto";import d from"node:fs";import u from"node:path";import{recordPersonalSyncLocalMetric as C}from"../personal-sync/metrics.js";import{resolvePersonalSyncV1Path as P}from"../personal-sync/paths.js";import{getCanonicalSessionStore as H,getDaemonInstallationId as U,listSessionRecords as K,recordSession as w}from"../session/store.js";import{readToolDetail as V,writeToolDetail as q}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as J}from"./native-source-deletions.js";import{isInternalNativeSource as W}from"./source-visibility.js";const E=1;class z extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class G extends Error{code="internal_native_source";constructor(){super("internal native source cannot be imported as a personal session"),this.name="InternalNativeSourceError"}}class Q extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class X extends Error{sourceEventKey;code="native_event_identity_conflict";constructor(e){super(`native event identity conflict: ${e}`),this.sourceEventKey=e,this.name="NativeEventIdentityConflictError"}}class pe{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].filter(s=>!this.isSourceInternal(s.agentType,s.sourceSessionKey)).sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}updateMessageDeliveryState(e,s,o){if(!this.canonicalStore().updateMessageDeliveryState(e,s,o))return;const i=(this.options.listLocalSessions??K)().find(a=>a.sessionId===e);if(!i)return;(this.options.recordLocalSession??w)({...i})}commitEvent(e){if(Y(e),this.isSourceInternal(e.agentType,e.sourceSessionKey))throw new G;if(this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new Q(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),n=b(e.agentType,e.sourceSessionKey),i=o.sources[n],c=se(e),a=this.ledgerFile(e.agentType,e.sourceEventKey),r=d.existsSync(a)?D(a):null;if(r&&r.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(r&&r.eventFingerprint!==c)throw new X(e.sourceEventKey);if(!i&&!r&&e.role!=="user")throw new z(e.sourceSessionKey);const g=e.claimSessionId??null,l=i?.sessionId??r?.sessionId??g??Z(s,e.agentType,e.sourceSessionKey);if(g&&i&&i.sessionId!==g)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const f=this.canonicalStore(),I=e.localDisposition==="managed_echo_suppressed"||r?.mappedBy==="managed_echo_suppressed",m=r?.canonicalMessageId??(I?null:e.claimCanonicalMessageId??ee(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let h=f.getManifest(l)?.bodyRevision??0,T=!!r;const M=!I&&!!e.claimCanonicalMessageId;if(M){if(!m)throw new Error("managed echo claim requires a canonical message");const S=f.getMessageState(l,m);if(!S||S.tombstoned)throw new Error(`managed echo claim target is not durable: ${m}`);S.deliveryState==="queued"&&S.message&&(h=f.upsert(l,{...S.message,ts:e.ts},"executing").bodyRevision)}if(!r&&!M&&!I){if(!m)throw new Error("native event requires a canonical message");const S=e.canonicalMessageKey?f.getMessageState(l,m):null,F=e.canonicalMutation==="upsert"||!!S,_=F?S??f.getMessageState(l,m):null,v={id:m,sessionId:l,role:e.role,ts:_?.message?.ts??e.ts,payload:te(_?.message?.payload,e.payload)},$=F?f.upsert(l,v):f.append(l,v);if(h=$.bodyRevision,T=$.duplicate,e.localToolDetail){const k=this.options.writeLocalToolDetail??q,j=this.options.readLocalToolDetail??V,R=oe(j(l,e.localToolDetail.toolCallId),e.localToolDetail);k({sessionId:l,...R,ts:e.ts,detailRef:{toolIndex:0}})}}e.terminal&&(h=f.completeStartedUserMessages(l,e.ts).at(-1)?.bodyRevision??h),(!r||e.terminal)&&!I&&this.recordIndex(e,l,m,h);const x=new Date().toISOString();return r||p(a,{schemaVersion:E,daemonInstallationId:s,sessionId:l,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:c,canonicalMessageId:m,canonicalRevision:h,cursor:e.cursor??null,mappedBy:I?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:x}),o.sources[n]={schemaVersion:E,daemonInstallationId:s,sessionId:l,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:r?i?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:r?i?.lastCursor??r.cursor:e.cursor??i?.lastCursor??null,bodyRevision:h,createdAt:i?.createdAt??x,updatedAt:x},p(this.sourcesFile(),o),!r&&e.cursor&&C("sourceCursorAdvanced"),!r&&!M&&!I&&!T&&C("canonicalCommits"),{sessionId:l,messageId:m,bodyRevision:h,duplicate:T,claimed:M,suppressed:I}}repairMissingUserBoundaries(){const e=this.options.listLocalSessions??K,s=this.options.recordLocalSession??w,o=this.canonicalStore();let n=0;for(const i of e()){if(i.localReady)continue;const a=o.getManifest(i.sessionId)?.firstAvailableSeq;if(a==null)continue;const r=o.readPage(i.sessionId,{limit:1,beforeSequence:a+1}).entries[0];!r||r.sequence!==a||r.message.role!=="user"||(s({sessionId:i.sessionId,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:i.modelId,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId,status:i.status,lastActivityAt:i.lastActivityAt,lastMessagePreview:i.lastMessagePreview,localReady:!0,firstUserMessageId:r.message.id,firstUserBodyRevision:r.revision}),n+=1)}return n}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){A(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,p(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const n=this.readManagedEchoState();let i=!1;for(const c of Object.values(n.claims))c.sessionId!==e||c.sourceAgentType!==s||(c.sourceSessionKey=o,i=!0);i&&p(this.managedEchoStateFile(),n)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],p(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(N(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),p(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),n=b(e,s);o.suppressions[n]&&(delete o.suppressions[n],p(this.managedEchoStateFile(),o))}listSourceBindings(){return Object.values(this.readSources().sources).filter(e=>!this.isSourceDeleted(e.agentType,e.sourceSessionKey))}purgeSession(e){if(!e)throw new Error("sessionId is required");const s=this.readSources();let o=!1;for(const[a,r]of Object.entries(s.sources))r.sessionId===e&&(delete s.sources[a],o=!0);o&&p(this.sourcesFile(),s);const n=this.readManagedEchoState();let i=!1;for(const[a,r]of Object.entries(n.claims))r.sessionId===e&&(delete n.claims[a],i=!0);for(const[a,r]of Object.entries(n.suppressions))r.sessionId===e&&(delete n.suppressions[a],i=!0);i&&p(this.managedEchoStateFile(),n);const c=u.join(this.rootDir(),"ledger");if(d.existsSync(c))for(const a of d.readdirSync(c,{withFileTypes:!0})){if(!a.isFile()||!a.name.endsWith(".json"))continue;const r=u.join(c,a.name),g=D(r);if(g.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");g.sessionId===e&&d.unlinkSync(r)}}isSourceInternal(e,s){const o=this.rootDir();return W(e,s,{filePath:u.join(u.basename(o)==="native-fusion"?u.dirname(o):o,"native-fusion-state.json"),daemonInstallationId:this.installationId()})}isSourceDeleted(e,s){return(this.options.deletionStore??new J({rootDir:u.basename(this.rootDir())==="native-fusion"?u.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){if(this.isSourceInternal(e,s))return!1;const n=o.trim().slice(0,120);if(!n)return!1;const i=this.readSources().sources[b(e,s)];if(!i||this.isSourceDeleted(e,s))return!1;const c=(this.options.listLocalSessions??K)().find(g=>g.sessionId===i.sessionId);if(c?.titleLockedByUser||c?.titleSource==="agent_native"&&c.title===n)return!1;const a=y.createHash("sha256").update(`${e}\0${s}\0${n}`).digest("hex");return(this.options.recordLocalSession??w)({sessionId:i.sessionId,agentType:i.agentType,workDir:c?.workDir??"",agentSessionId:s,title:n,titleSource:"agent_native",titleOriginEventId:`agent-native:${a}`}),!0}recordIndex(e,s,o,n){const i=this.options.recordLocalSession??w,c=e.terminal?`terminal:${y.createHash("sha256").update(e.sourceEventKey).digest("hex")}`:void 0,a=e.role==="user"||e.role==="agent"?O(e.payload):null,r=a&&o?{lastMessagePreview:a,lastMessagePreviewMessageId:o,lastMessagePreviewAt:new Date(e.ts).toISOString()}:{},g=o&&(a||B(e.payload))?new Date(e.ts).toISOString():void 0;i({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?e.terminalState==="failed"||e.terminalState==="aborted"?"failed":"completed":"active",attentionId:c,lastActivityAt:new Date(e.ts).toISOString(),lastVisibleEventAt:g,...r,...e.role==="user"&&o?{localReady:!0,firstUserMessageId:o,firstUserBodyRevision:n}:{},title:e.title?.trim()||(e.role==="user"?O(e.payload)??"":void 0),titleSource:e.title?.trim()?e.titleSource??"first_user_fallback":"first_user_fallback",titleOriginEventId:e.sourceEventKey})}readSources(){const e=this.sourcesFile();if(!d.existsSync(e))return{schemaVersion:E,daemonInstallationId:this.installationId(),sources:{}};const s=D(e);if(s.schemaVersion!==E||s.daemonInstallationId!==this.installationId()||!s.sources)throw new Error("invalid native source binding store");for(const o of Object.values(s.sources))if(o.daemonInstallationId!==this.installationId())throw new Error("native source binding belongs to another daemon installation");return s}installationId(){return this.options.daemonInstallationId??U()}canonicalStore(){return this.options.canonicalStore??H()}rootDir(){return this.options.rootDir??P("native-fusion")}sourcesFile(){return u.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return u.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!d.existsSync(e))return{schemaVersion:E,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=D(e);if(s.schemaVersion!==E||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))A(o);for(const o of Object.values(s.suppressions))N(o);return s}ledgerFile(e,s){const o=y.createHash("sha256").update(`${e}\0${s}`).digest("hex");return u.join(this.rootDir(),"ledger",`${o}.json`)}}function Y(t){if(!t.agentType||!t.sourceSessionKey||!t.sourceEventKey||!t.payload)throw new Error("invalid native event");if(!Number.isFinite(t.ts))throw new Error("invalid native event timestamp")}function b(t,e){return y.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function Z(t,e,s){return`native-session-${y.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function ee(t,e,s){return`native-${y.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function se(t){return y.createHash("sha256").update(JSON.stringify({agentType:t.agentType,sourceSessionKey:t.sourceSessionKey,sourceEventKey:t.sourceEventKey,role:t.role,ts:t.ts,payload:t.payload,canonicalMessageKey:t.canonicalMessageKey??null,canonicalMutation:t.canonicalMutation??"append"})).digest("hex")}function A(t){if(!t.sessionId||!t.canonicalMessageId||!t.textHash||!Number.isFinite(t.createdAt)||!["codex","claude","opencode"].includes(t.sourceAgentType)||!["suppress_following","import_following"].includes(t.managedEchoPolicy))throw new Error("invalid managed echo claim")}function N(t){if(!t.sessionId||!t.sourceSessionKey||!t.claimSourceEventKey||!["codex","claude","opencode"].includes(t.agentType)||!Number.isFinite(t.startedAt)||!Number.isFinite(t.endsAt)||t.endsAt<t.startedAt)throw new Error("invalid managed echo suppression")}function te(t,e){if(!t)return e;try{const s=JSON.parse(t),o=JSON.parse(e);if(s.type!=="tool"||o.type!=="tool"||s.toolCallId!==o.toolCallId)return e;const n=typeof o.name=="string"?o.name:"",i=/(?:_output|_result)$/.test(n);return JSON.stringify({...s,...o,...i&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function oe(t,e){if(!t||t.toolCallId!==e.toolCallId)return e;const s=/(?:_output|_result)$/.test(e.name);return{...e,name:s?t.name:e.name,...e.args===void 0&&t.args!==void 0?{args:t.args}:{},...e.result===void 0&&t.result!==void 0?{result:t.result}:{}}}function O(t){return L(t,120)}function p(t,e){const s=u.dirname(t);d.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${y.randomBytes(6).toString("hex")}`;let n;try{n=d.openSync(o,"wx",384),d.writeFileSync(n,`${JSON.stringify(e,null,2)}
2
+ `,"utf8"),d.fsyncSync(n),d.closeSync(n),n=void 0,d.renameSync(o,t)}finally{n!==void 0&&d.closeSync(n);try{d.unlinkSync(o)}catch{}}}function D(t){return JSON.parse(d.readFileSync(t,"utf8"))}export{G as InternalNativeSourceError,z as MissingUserBoundaryError,X as NativeEventIdentityConflictError,pe as NativeFusionLocalRepository,Q as NativeSourceDeletedError};
@@ -17,6 +17,7 @@ export declare class NativeSessionFusionService {
17
17
  private suppressionWindows;
18
18
  private externalObservations;
19
19
  private externalTerminalHandler;
20
+ private externalClaimHandler;
20
21
  private ignoredSources;
21
22
  constructor(client: CliRelayClient, repository?: NativeFusionLocalRepository);
22
23
  /** Durably excludes short-lived validation/orchestration sessions from personal chat discovery. */
@@ -24,6 +25,7 @@ export declare class NativeSessionFusionService {
24
25
  start(): void;
25
26
  stop(): void;
26
27
  setExternalTerminalHandler(handler: ((sessionId: string) => void) | null): void;
28
+ setExternalClaimHandler(handler: ((sessionId: string, canonicalMessageId: string) => void) | null): void;
27
29
  restoreExternalObservation(params: {
28
30
  sessionId: string;
29
31
  sourceSessionKey: string;