shennian 0.3.54 → 0.3.56

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.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "jsMinified": true,
4
4
  "minifier": "esbuild",
5
- "fileCount": 125,
5
+ "fileCount": 126,
6
6
  "files": [
7
7
  {
8
8
  "file": "bin/shennian.js",
@@ -276,8 +276,8 @@
276
276
  },
277
277
  {
278
278
  "file": "src/index.js",
279
- "beforeBytes": 22986,
280
- "afterBytes": 11906
279
+ "beforeBytes": 23134,
280
+ "afterBytes": 12013
281
281
  },
282
282
  {
283
283
  "file": "src/log-reporter.js",
@@ -321,8 +321,8 @@
321
321
  },
322
322
  {
323
323
  "file": "src/native-fusion/local-repository.js",
324
- "beforeBytes": 20835,
325
- "afterBytes": 10716
324
+ "beforeBytes": 21227,
325
+ "afterBytes": 10888
326
326
  },
327
327
  {
328
328
  "file": "src/native-fusion/native-source-deletions.js",
@@ -374,6 +374,11 @@
374
374
  "beforeBytes": 1928,
375
375
  "afterBytes": 1024
376
376
  },
377
+ {
378
+ "file": "src/personal-sync/metrics.js",
379
+ "beforeBytes": 1504,
380
+ "afterBytes": 904
381
+ },
377
382
  {
378
383
  "file": "src/personal-sync/paths.js",
379
384
  "beforeBytes": 1345,
@@ -571,13 +576,13 @@
571
576
  },
572
577
  {
573
578
  "file": "src/session/history-subscriptions.js",
574
- "beforeBytes": 6516,
575
- "afterBytes": 3289
579
+ "beforeBytes": 7483,
580
+ "afterBytes": 3798
576
581
  },
577
582
  {
578
583
  "file": "src/session/index-publisher.js",
579
- "beforeBytes": 3011,
580
- "afterBytes": 1330
584
+ "beforeBytes": 3275,
585
+ "afterBytes": 1470
581
586
  },
582
587
  {
583
588
  "file": "src/session/manager.js",
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{Command as te}from"commander";import S from"chalk";import re from"node:os";import b from"node:fs";import{loadConfig as d,saveConfig as ie,getConfigPath as se,getShennianDir as ae,resolveShennianPath as J,normalizeAutoUpgradePolicy as ce}from"./config/index.js";import{CliRelayClient as me}from"./relay/client.js";import{registerPairCommand as le,runSmartStart as de}from"./commands/pair.js";import{clearDaemonPidIfOwner as z,createDaemonInstanceId as pe,clearDaemonLauncher as j,findRunningDaemonProcessIds as ge,isRemoteAccessDisabled as ue,isIsolatedDaemonE2eInstance as he,registerDaemonCommand as fe,writeDaemonPid as K,writeDaemonLauncher as W}from"./commands/daemon.js";import{registerRuntimeCommand as ve}from"./commands/runtime.js";import{registerManagerCommand as Se}from"./commands/manager.js";import{registerUpgradeCommand as we}from"./commands/upgrade.js";import{registerToolsCommand as Ie}from"./commands/tools.js";import{registerIntegrationCommand as ye}from"./commands/integration.js";import{registerRoomCommand as Re}from"./commands/room.js";import{SessionManager as Ce}from"./session/manager.js";import{SERVERS as P,regionToUrl as ke,urlToRegion as De}from"./region.js";import{getCurrentVersion as Y,handleStartupCrashCheck as Ee}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Te}from"./upgrade/scheduler.js";import{detectAgents as Ue}from"./agents/detect.js";import{augmentProcessPath as Q}from"./env-path.js";Q();const q=Y();import{getCachedAgentInfos as Ae,resolveAgentInfos as Ne}from"./agents/model-registry.js";import{initCliLogReporter as Oe,reportLog as w}from"./log-reporter.js";import{isNativeFusionEnabled as $e}from"./native-fusion/config.js";import{NativeSessionFusionService as be}from"./native-fusion/service.js";import{startDaemonLogRetention as Pe}from"./daemon-log.js";import{startDaemonIpcServer as _e}from"./daemon-ipc/server.js";import{RoomSessionRegistry as Me}from"./room/session-registry.js";import{HttpRoomBindingClient as Le}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as xe}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Ve}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Be}from"./room/mirror-sync-service.js";import{RoomActivationService as Ge}from"./room/activation-service.js";import{RoomActivationAudit as He}from"./room/activation-audit.js";import{RoomAttachmentCache as Fe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Je}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as ze}from"./room/managed-context.js";import{createManagedRoomIpcHandlers as je}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as Ke,SESSION_SYNC_REQUIRED_CAPABILITIES as We}from"@shennian/wire";import{getDaemonInstallationId as Ye}from"./session/store.js";import{synchronizeActiveAccountNamespace as Qe}from"./personal-sync/account-namespace.js";const X=ae(),qe=J("daemon.pid");function Xe(){try{const o=b.readFileSync(qe,"utf-8").trim();if(o.startsWith("{")){const r=JSON.parse(o),c=Number(r.pid);return Number.isInteger(c)&&c>0?c:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function Ze(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function _(){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 eo(o){const n=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&n.push(`reason=${o.reason}`),o.error&&n.push(`error=${o.error}`),n.join(" ")}async function Z(o,n=5e3){const r=Date.now();for(;Date.now()-r<n;){try{process.kill(o,0)}catch{return!0}await new Promise(c=>setTimeout(c,100))}try{return process.kill(o,0),!1}catch{return!0}}const t=new te;t.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(q),t.enablePositionalOptions(),ye(t),Re(t),t.option("--api <url>","Server URL override").option("--name <name>","Machine name",re.hostname()).action(async o=>{const n=d(),r=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await de(r,o.name)}),t.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const n=J("env.json");try{const e=JSON.parse(b.readFileSync(n,"utf-8"));for(const[i,a]of Object.entries(e))process.env[i]||(process.env[i]=a);Q()}catch{}const r=Pe();ue()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(r),process.exit(0));const c=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),M=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await Z(e)||(process.kill(e,"SIGKILL"),await Z(e,2e3))};try{const e=Xe();if(e&&e!==process.pid)try{process.kill(e,0),c?await M(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const U=he(process.env,X)?[]:ge(process.pid);if(U.length>0)if(c)for(const e of U)try{await M(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${U[0]}), skipping duplicate start`),process.exit(0);const l=pe();K(process.pid,l,{version:q}),W(process.pid,void 0,l),process.on("exit",()=>{z(process.pid,l),j(l)}),await Ee()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));const s=d(),g=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??P.cn.url,p=Y(),A=Ue(),h=A.map(e=>e.type),N=Ae(A),I=new Me,y=new Ve,L=new ze(I),f=new Le(g),oe=new Fe({actionClient:f}),ne=new Je({actionClient:f});let x=null;const R=await _e({handlers:{...je({authority:L,actionClient:f,attachmentCache:oe,attachmentUploader:ne,observations:y,getMachineCredentials:()=>({machineToken:d().machineToken})})}});let V=!1;const C=()=>{V||(V=!0,R.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",C),process.once("SIGTERM",C),!s.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await R.close(),process.exitCode=1;return}process.removeListener("SIGINT",C),process.removeListener("SIGTERM",C);const B=`${Ze(g)}/relay/machine`,O=s.machineToken;if(!O)return;try{s.accountNamespaceId=await Qe({serverUrl:g,machineToken:O,machineId:s.machineId})}catch(e){console.error(`[personal-sync] account namespace verification failed: ${e instanceof Error?e.message:String(e)}`),await R.close(),process.exitCode=1;return}s.machineId&&Oe(g,s.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${B}... (v${p}) agents: ${h.join(",")}`),w({level:"info",wsEvent:"daemon.start",metadata:{version:p,agents:h}});let k=null,D=null,E=0;const G=ce(s.autoUpgrade),m=new me({serverUrl:B,machineToken:O,cliVersion:p,agentList:h,autoUpgradePolicy:G,sessionSyncProtocolVersion:Ke,sessionSyncCapabilities:We,daemonInstallationId:Ye(),onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),w({level:"info",wsEvent:"daemon.connected"}),N.some(a=>a.models.length>0)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:h,agents:N}}),Ne(A,{serverUrl:s.serverUrl??g,authToken:s.machineToken??s.accessToken}).then(a=>{JSON.stringify(a)!==JSON.stringify(N)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:h,agents:a}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:a,clearUpgradeAttempt:$})=>{const v=a();$(),v&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${v.from} \u2192 ${v.to}`),m.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:v.from,to:v.to}}))}).catch(()=>{}),T.start(),D?.start();const e=++E,i=async()=>{try{await u.synchronizeSessionDeletions(),e===E&&m.getState()==="connected"&&k?.handleConnected()}catch(a){console.error("[session.deletions] startup catch-up failed",a),e===E&&m.getState()==="connected"&&setTimeout(()=>{i()},5e3).unref?.()}};i()},onDisconnected:e=>{E+=1,D?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${eo(e)}`),w({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),w({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),u.handleReq(e)}});k=$e()?new be(m):null,D=new Be({registry:I,observations:y,actionClient:f,getMachineCredentials:()=>({machineToken:d().machineToken}),onMessages:async(e,i,a,$)=>{await x?.processMessages(e,i,a,$)}});const T=new Te({currentVersion:p,machineId:s.machineId??"unknown-machine",serverUrl:s.serverUrl??g,getIdleState:()=>u.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(m,"auto-upgrade",e,{currentVersion:p})}});T.setPolicy(G);const u=new Ce(m,k,p,T,new xe({registry:I,observations:y,bindingClient:f,getMachineCredentials:()=>{const e=d();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${l}`}));x=new Ge({registry:I,observations:y,activate:e=>u.activateRoomSession(e),issueManagedRoomContext:e=>{const i=d().machineToken;if(!i)throw new Error("machine_not_authorized");return L.issue({registryKey:e,machineCredential:i})},abort:e=>u.abortRoomSession(e),audit:new He}),b.mkdirSync(X,{recursive:!0}),K(process.pid,l,{version:p}),W(process.pid,void 0,l),m.connect(),process.stdin.resume();let H=!1;const F=async()=>{if(!H){H=!0,console.log(S.gray(`
1
+ import{Command as te}from"commander";import S from"chalk";import re from"node:os";import b from"node:fs";import{loadConfig as d,saveConfig as ie,getConfigPath as se,getShennianDir as ae,resolveShennianPath as J,normalizeAutoUpgradePolicy as ce}from"./config/index.js";import{CliRelayClient as me}from"./relay/client.js";import{registerPairCommand as le,runSmartStart as de}from"./commands/pair.js";import{clearDaemonPidIfOwner as z,createDaemonInstanceId as pe,clearDaemonLauncher as j,findRunningDaemonProcessIds as ge,isRemoteAccessDisabled as ue,isIsolatedDaemonE2eInstance as he,registerDaemonCommand as fe,writeDaemonPid as K,writeDaemonLauncher as W}from"./commands/daemon.js";import{registerRuntimeCommand as ve}from"./commands/runtime.js";import{registerManagerCommand as Se}from"./commands/manager.js";import{registerUpgradeCommand as we}from"./commands/upgrade.js";import{registerToolsCommand as Ie}from"./commands/tools.js";import{registerIntegrationCommand as ye}from"./commands/integration.js";import{registerRoomCommand as Re}from"./commands/room.js";import{SessionManager as Ce}from"./session/manager.js";import{SERVERS as P,regionToUrl as ke,urlToRegion as De}from"./region.js";import{getCurrentVersion as Y,handleStartupCrashCheck as Ee}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Te}from"./upgrade/scheduler.js";import{detectAgents as Ue}from"./agents/detect.js";import{augmentProcessPath as Q}from"./env-path.js";Q();const q=Y();import{getCachedAgentInfos as Ae,resolveAgentInfos as Ne}from"./agents/model-registry.js";import{initCliLogReporter as Oe,reportLog as w}from"./log-reporter.js";import{isNativeFusionEnabled as $e}from"./native-fusion/config.js";import{NativeSessionFusionService as be}from"./native-fusion/service.js";import{startDaemonLogRetention as Pe}from"./daemon-log.js";import{startDaemonIpcServer as _e}from"./daemon-ipc/server.js";import{RoomSessionRegistry as Me}from"./room/session-registry.js";import{HttpRoomBindingClient as Le}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as xe}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Ve}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Be}from"./room/mirror-sync-service.js";import{RoomActivationService as Ge}from"./room/activation-service.js";import{RoomActivationAudit as He}from"./room/activation-audit.js";import{RoomAttachmentCache as Fe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Je}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as ze}from"./room/managed-context.js";import{createManagedRoomIpcHandlers as je}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as Ke,SESSION_SYNC_REQUIRED_CAPABILITIES as We}from"@shennian/wire";import{getDaemonInstallationId as Ye}from"./session/store.js";import{synchronizeActiveAccountNamespace as Qe}from"./personal-sync/account-namespace.js";import{getPersonalSyncLocalMetrics as qe}from"./personal-sync/metrics.js";const X=ae(),Xe=J("daemon.pid");function Ze(){try{const o=b.readFileSync(Xe,"utf-8").trim();if(o.startsWith("{")){const r=JSON.parse(o),c=Number(r.pid);return Number.isInteger(c)&&c>0?c:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function eo(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function _(){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 oo(o){const n=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&n.push(`reason=${o.reason}`),o.error&&n.push(`error=${o.error}`),n.join(" ")}async function Z(o,n=5e3){const r=Date.now();for(;Date.now()-r<n;){try{process.kill(o,0)}catch{return!0}await new Promise(c=>setTimeout(c,100))}try{return process.kill(o,0),!1}catch{return!0}}const t=new te;t.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(q),t.enablePositionalOptions(),ye(t),Re(t),t.option("--api <url>","Server URL override").option("--name <name>","Machine name",re.hostname()).action(async o=>{const n=d(),r=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await de(r,o.name)}),t.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const n=J("env.json");try{const e=JSON.parse(b.readFileSync(n,"utf-8"));for(const[i,a]of Object.entries(e))process.env[i]||(process.env[i]=a);Q()}catch{}const r=Pe();ue()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(r),process.exit(0));const c=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),M=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await Z(e)||(process.kill(e,"SIGKILL"),await Z(e,2e3))};try{const e=Ze();if(e&&e!==process.pid)try{process.kill(e,0),c?await M(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const U=he(process.env,X)?[]:ge(process.pid);if(U.length>0)if(c)for(const e of U)try{await M(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${U[0]}), skipping duplicate start`),process.exit(0);const l=pe();K(process.pid,l,{version:q}),W(process.pid,void 0,l),process.on("exit",()=>{z(process.pid,l),j(l)}),await Ee()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));const s=d(),g=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??P.cn.url,p=Y(),A=Ue(),h=A.map(e=>e.type),N=Ae(A),I=new Me,y=new Ve,L=new ze(I),f=new Le(g),oe=new Fe({actionClient:f}),ne=new Je({actionClient:f});let x=null;const R=await _e({handlers:{"personal.sync.metrics":()=>qe(),...je({authority:L,actionClient:f,attachmentCache:oe,attachmentUploader:ne,observations:y,getMachineCredentials:()=>({machineToken:d().machineToken})})}});let V=!1;const C=()=>{V||(V=!0,R.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",C),process.once("SIGTERM",C),!s.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await R.close(),process.exitCode=1;return}process.removeListener("SIGINT",C),process.removeListener("SIGTERM",C);const B=`${eo(g)}/relay/machine`,O=s.machineToken;if(!O)return;try{s.accountNamespaceId=await Qe({serverUrl:g,machineToken:O,machineId:s.machineId})}catch(e){console.error(`[personal-sync] account namespace verification failed: ${e instanceof Error?e.message:String(e)}`),await R.close(),process.exitCode=1;return}s.machineId&&Oe(g,s.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${B}... (v${p}) agents: ${h.join(",")}`),w({level:"info",wsEvent:"daemon.start",metadata:{version:p,agents:h}});let k=null,D=null,E=0;const G=ce(s.autoUpgrade),m=new me({serverUrl:B,machineToken:O,cliVersion:p,agentList:h,autoUpgradePolicy:G,sessionSyncProtocolVersion:Ke,sessionSyncCapabilities:We,daemonInstallationId:Ye(),onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),w({level:"info",wsEvent:"daemon.connected"}),N.some(a=>a.models.length>0)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:h,agents:N}}),Ne(A,{serverUrl:s.serverUrl??g,authToken:s.machineToken??s.accessToken}).then(a=>{JSON.stringify(a)!==JSON.stringify(N)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:h,agents:a}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:a,clearUpgradeAttempt:$})=>{const v=a();$(),v&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${v.from} \u2192 ${v.to}`),m.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:v.from,to:v.to}}))}).catch(()=>{}),T.start(),D?.start();const e=++E,i=async()=>{try{await u.synchronizeSessionDeletions(),e===E&&m.getState()==="connected"&&k?.handleConnected()}catch(a){console.error("[session.deletions] startup catch-up failed",a),e===E&&m.getState()==="connected"&&setTimeout(()=>{i()},5e3).unref?.()}};i()},onDisconnected:e=>{E+=1,D?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${oo(e)}`),w({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),w({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),u.handleReq(e)}});k=$e()?new be(m):null,D=new Be({registry:I,observations:y,actionClient:f,getMachineCredentials:()=>({machineToken:d().machineToken}),onMessages:async(e,i,a,$)=>{await x?.processMessages(e,i,a,$)}});const T=new Te({currentVersion:p,machineId:s.machineId??"unknown-machine",serverUrl:s.serverUrl??g,getIdleState:()=>u.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(m,"auto-upgrade",e,{currentVersion:p})}});T.setPolicy(G);const u=new Ce(m,k,p,T,new xe({registry:I,observations:y,bindingClient:f,getMachineCredentials:()=>{const e=d();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${l}`}));x=new Ge({registry:I,observations:y,activate:e=>u.activateRoomSession(e),issueManagedRoomContext:e=>{const i=d().machineToken;if(!i)throw new Error("machine_not_authorized");return L.issue({registryKey:e,machineCredential:i})},abort:e=>u.abortRoomSession(e),audit:new He}),b.mkdirSync(X,{recursive:!0}),K(process.pid,l,{version:p}),W(process.pid,void 0,l),m.connect(),process.stdin.resume();let H=!1;const F=async()=>{if(!H){H=!0,console.log(S.gray(`
3
3
  Disconnecting...`)),w({level:"info",wsEvent:"daemon.stop"}),clearInterval(r);try{await u.cleanup(),T.stop(),k?.stop(),D?.stop(),m.disconnect(),await R.close(),z(process.pid,l),j(l)}finally{process.exit(0)}}};process.on("SIGINT",()=>{F()}),process.on("SIGTERM",()=>{F()})}),t.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>_()),t.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(()=>_()),t.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>_());const ee=t.command("config").description("View or modify configuration");ee.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${se()}`);const n={...d()};if(n.machineToken&&(n.machineToken=n.machineToken.slice(0,12)+"..."),n.serverUrl){const r=De(n.serverUrl);console.log(`Region: ${r} (${P[r].label})`)}console.log(JSON.stringify(n,null,2))}),ee.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,n)=>{const r=d();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(S.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=n;r.serverUrl=ke(c),ie(r),console.log(S.green(`\u2713 Server set to ${P[c].label}`)),console.log(S.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(S.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),le(t),fe(t),ve(t),Se(t),we(t),Ie(t),t.parse();
@@ -1,2 +1,2 @@
1
- import p from"node:crypto";import l from"node:fs";import h from"node:path";import{sessionPreviewFromPayload as k}from"@shennian/wire";import{resolvePersonalSyncV1Path as A}from"../personal-sync/paths.js";import{getCanonicalSessionStore as v,getDaemonInstallationId as B,listSessionRecords as H,recordSession as $}from"../session/store.js";import{readToolDetail as J,writeToolDetail as L}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as R}from"./native-source-deletions.js";const y=1;class V extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}class P extends Error{code="native_source_deleted";constructor(e){super(`native source ${e} is blocked by a durable deletion tombstone`),this.name="NativeSourceDeletedError"}}class ne{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}commitEvent(e){if(q(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new P(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),i=b(e.agentType,e.sourceSessionKey),r=o.sources[i],a=z(e),c=this.ledgerFile(e.agentType,e.sourceEventKey),n=l.existsSync(c)?w(c):null;if(n&&n.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(n&&n.eventFingerprint!==a)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!r&&!n&&e.role!=="user")throw new V(e.sourceSessionKey);const m=e.claimSessionId??null,d=r?.sessionId??n?.sessionId??m??U(s,e.agentType,e.sourceSessionKey);if(m&&r&&r.sessionId!==m)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const S=this.canonicalStore(),f=e.localDisposition==="managed_echo_suppressed"||n?.mappedBy==="managed_echo_suppressed",g=n?.canonicalMessageId??(f?null:e.claimCanonicalMessageId??W(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let E=S.getManifest(d)?.bodyRevision??0,T=!!n;const M=!f&&!!e.claimCanonicalMessageId;if(M){if(!g)throw new Error("managed echo claim requires a canonical message");const I=S.getMessageState(d,g);if(!I||I.tombstoned)throw new Error(`managed echo claim target is not durable: ${g}`)}if(!n&&!M&&!f){if(!g)throw new Error("native event requires a canonical message");const I=e.canonicalMutation==="upsert"?S.getMessageState(d,g):null,F={id:g,sessionId:d,role:e.role,ts:I?.message?.ts??e.ts,payload:G(I?.message?.payload,e.payload)},K=e.canonicalMutation==="upsert"?S.upsert(d,F):S.append(d,F);if(E=K.bodyRevision,T=K.duplicate,e.localToolDetail){const C=this.options.writeLocalToolDetail??L,j=this.options.readLocalToolDetail??J,N=Q(j(d,e.localToolDetail.toolCallId),e.localToolDetail);C({sessionId:d,...N,ts:e.ts,detailRef:{toolIndex:0}})}}!n&&!f&&this.recordIndex(e,d);const D=new Date().toISOString();return n||u(c,{schemaVersion:y,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:a,canonicalMessageId:g,canonicalRevision:E,cursor:e.cursor??null,mappedBy:f?"managed_echo_suppressed":M?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:D}),o.sources[i]={schemaVersion:y,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:n?r?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:n?r?.lastCursor??n.cursor:e.cursor??r?.lastCursor??null,bodyRevision:E,createdAt:r?.createdAt??D,updatedAt:D},u(this.sourcesFile(),o),{sessionId:d,messageId:g,bodyRevision:E,duplicate:T,claimed:M,suppressed:f}}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){x(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,u(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const i=this.readManagedEchoState();let r=!1;for(const a of Object.values(i.claims))a.sessionId!==e||a.sourceAgentType!==s||(a.sourceSessionKey=o,r=!0);r&&u(this.managedEchoStateFile(),i)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],u(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(_(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),u(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),i=b(e,s);o.suppressions[i]&&(delete o.suppressions[i],u(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[c,n]of Object.entries(s.sources))n.sessionId===e&&(delete s.sources[c],o=!0);o&&u(this.sourcesFile(),s);const i=this.readManagedEchoState();let r=!1;for(const[c,n]of Object.entries(i.claims))n.sessionId===e&&(delete i.claims[c],r=!0);for(const[c,n]of Object.entries(i.suppressions))n.sessionId===e&&(delete i.suppressions[c],r=!0);r&&u(this.managedEchoStateFile(),i);const a=h.join(this.rootDir(),"ledger");if(l.existsSync(a))for(const c of l.readdirSync(a,{withFileTypes:!0})){if(!c.isFile()||!c.name.endsWith(".json"))continue;const n=h.join(a,c.name),m=w(n);if(m.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");m.sessionId===e&&l.unlinkSync(n)}}isSourceDeleted(e,s){return(this.options.deletionStore??new R({rootDir:h.basename(this.rootDir())==="native-fusion"?h.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const i=o.trim().slice(0,120);if(!i)return!1;const r=this.readSources().sources[b(e,s)];if(!r||this.isSourceDeleted(e,s))return!1;const a=(this.options.listLocalSessions??H)().find(m=>m.sessionId===r.sessionId);if(a?.titleLockedByUser||a?.titleSource==="agent_native"&&a.title===i)return!1;const c=p.createHash("sha256").update(`${e}\0${s}\0${i}`).digest("hex");return(this.options.recordLocalSession??$)({sessionId:r.sessionId,agentType:r.agentType,workDir:a?.workDir??"",agentSessionId:s,title:i,titleSource:"agent_native",titleOriginEventId:`agent-native:${c}`}),!0}recordIndex(e,s){(this.options.recordLocalSession??$)({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?"completed":"active",lastActivityAt:new Date(e.ts).toISOString(),lastMessagePreview:e.role==="user"?O(e.payload):void 0,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(!l.existsSync(e))return{schemaVersion:y,daemonInstallationId:this.installationId(),sources:{}};const s=w(e);if(s.schemaVersion!==y||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??B()}canonicalStore(){return this.options.canonicalStore??v()}rootDir(){return this.options.rootDir??A("native-fusion")}sourcesFile(){return h.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return h.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:y,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=w(e);if(s.schemaVersion!==y||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))x(o);for(const o of Object.values(s.suppressions))_(o);return s}ledgerFile(e,s){const o=p.createHash("sha256").update(`${e}\0${s}`).digest("hex");return h.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 p.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function U(t,e,s){return`native-session-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function W(t,e,s){return`native-${p.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function z(t){return p.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 x(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 _(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 G(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 i=typeof o.name=="string"?o.name:"",r=/(?:_output|_result)$/.test(i);return JSON.stringify({...s,...o,...r&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function Q(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 k(t,120)}function u(t,e){const s=h.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;let i;try{i=l.openSync(o,"wx",384),l.writeFileSync(i,`${JSON.stringify(e,null,2)}
2
- `,"utf8"),l.fsyncSync(i),l.closeSync(i),i=void 0,l.renameSync(o,t)}finally{i!==void 0&&l.closeSync(i);try{l.unlinkSync(o)}catch{}}}function w(t){return JSON.parse(l.readFileSync(t,"utf8"))}export{V as MissingUserBoundaryError,ne as NativeFusionLocalRepository,P as NativeSourceDeletedError};
1
+ import f from"node:crypto";import l from"node:fs";import h from"node:path";import{sessionPreviewFromPayload as A}from"@shennian/wire";import{resolvePersonalSyncV1Path as v}from"../personal-sync/paths.js";import{getCanonicalSessionStore as B,getDaemonInstallationId as H,listSessionRecords as L,recordSession as $}from"../session/store.js";import{readToolDetail as J,writeToolDetail as R}from"../session/tool-detail-store.js";import{NativeSourceDeletionStore as V}from"./native-source-deletions.js";import{recordPersonalSyncLocalMetric as x}from"../personal-sync/metrics.js";const y=1;class P extends Error{code="missing_user_boundary";constructor(e){super(`native source ${e} does not start at a visible user boundary`),this.name="MissingUserBoundaryError"}}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 ae{options;constructor(e={}){this.options=e}commitEvents(e){return[...e].sort((s,o)=>s.ts-o.ts).map(s=>this.commitEvent(s))}commitEvent(e){if(U(e),this.isSourceDeleted(e.agentType,e.sourceSessionKey))throw new q(e.sourceSessionKey);const s=this.installationId(),o=this.readSources(),i=b(e.agentType,e.sourceSessionKey),r=o.sources[i],a=G(e),c=this.ledgerFile(e.agentType,e.sourceEventKey),n=l.existsSync(c)?w(c):null;if(n&&n.daemonInstallationId!==s)throw new Error("native event ledger belongs to another daemon installation");if(n&&n.eventFingerprint!==a)throw new Error(`native event identity conflict: ${e.sourceEventKey}`);if(!r&&!n&&e.role!=="user")throw new P(e.sourceSessionKey);const m=e.claimSessionId??null,d=r?.sessionId??n?.sessionId??m??W(s,e.agentType,e.sourceSessionKey);if(m&&r&&r.sessionId!==m)throw new Error(`managed claim session conflicts with source binding ${e.sourceSessionKey}`);const S=this.canonicalStore(),p=e.localDisposition==="managed_echo_suppressed"||n?.mappedBy==="managed_echo_suppressed",g=n?.canonicalMessageId??(p?null:e.claimCanonicalMessageId??z(s,e.agentType,e.canonicalMessageKey??e.sourceEventKey));let M=S.getManifest(d)?.bodyRevision??0,D=!!n;const I=!p&&!!e.claimCanonicalMessageId;if(I){if(!g)throw new Error("managed echo claim requires a canonical message");const E=S.getMessageState(d,g);if(!E||E.tombstoned)throw new Error(`managed echo claim target is not durable: ${g}`)}if(!n&&!I&&!p){if(!g)throw new Error("native event requires a canonical message");const E=e.canonicalMutation==="upsert"?S.getMessageState(d,g):null,F={id:g,sessionId:d,role:e.role,ts:E?.message?.ts??e.ts,payload:Q(E?.message?.payload,e.payload)},K=e.canonicalMutation==="upsert"?S.upsert(d,F):S.append(d,F);if(M=K.bodyRevision,D=K.duplicate,e.localToolDetail){const j=this.options.writeLocalToolDetail??R,N=this.options.readLocalToolDetail??J,k=X(N(d,e.localToolDetail.toolCallId),e.localToolDetail);j({sessionId:d,...k,ts:e.ts,detailRef:{toolIndex:0}})}}!n&&!p&&this.recordIndex(e,d);const T=new Date().toISOString();return n||u(c,{schemaVersion:y,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceEventKey:e.sourceEventKey,eventFingerprint:a,canonicalMessageId:g,canonicalRevision:M,cursor:e.cursor??null,mappedBy:p?"managed_echo_suppressed":I?"managed_echo_claim":e.sourceMode,eventTs:e.ts,createdAt:T}),o.sources[i]={schemaVersion:y,daemonInstallationId:s,sessionId:d,agentType:e.agentType,sourceSessionKey:e.sourceSessionKey,sourceMode:n?r?.sourceMode??e.sourceMode:e.sourceMode,lastCursor:n?r?.lastCursor??n.cursor:e.cursor??r?.lastCursor??null,bodyRevision:M,createdAt:r?.createdAt??T,updatedAt:T},u(this.sourcesFile(),o),!n&&e.cursor&&x("sourceCursorAdvanced"),!n&&!I&&!p&&!D&&x("canonicalCommits"),{sessionId:d,messageId:g,bodyRevision:M,duplicate:D,claimed:I,suppressed:p}}listManagedClaims(){return Object.values(this.readManagedEchoState().claims)}listManagedSuppressions(){return Object.values(this.readManagedEchoState().suppressions)}upsertManagedClaim(e){_(e);const s=this.readManagedEchoState();s.claims[e.canonicalMessageId]=e,u(this.managedEchoStateFile(),s)}updateManagedClaimSource(e,s,o){const i=this.readManagedEchoState();let r=!1;for(const a of Object.values(i.claims))a.sessionId!==e||a.sourceAgentType!==s||(a.sourceSessionKey=o,r=!0);r&&u(this.managedEchoStateFile(),i)}removeManagedClaim(e){const s=this.readManagedEchoState();s.claims[e]&&(delete s.claims[e],u(this.managedEchoStateFile(),s))}completeManagedClaim(e,s){const o=this.readManagedEchoState();delete o.claims[e],s&&(C(s),o.suppressions[b(s.agentType,s.sourceSessionKey)]=s),u(this.managedEchoStateFile(),o)}removeManagedSuppression(e,s){const o=this.readManagedEchoState(),i=b(e,s);o.suppressions[i]&&(delete o.suppressions[i],u(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[c,n]of Object.entries(s.sources))n.sessionId===e&&(delete s.sources[c],o=!0);o&&u(this.sourcesFile(),s);const i=this.readManagedEchoState();let r=!1;for(const[c,n]of Object.entries(i.claims))n.sessionId===e&&(delete i.claims[c],r=!0);for(const[c,n]of Object.entries(i.suppressions))n.sessionId===e&&(delete i.suppressions[c],r=!0);r&&u(this.managedEchoStateFile(),i);const a=h.join(this.rootDir(),"ledger");if(l.existsSync(a))for(const c of l.readdirSync(a,{withFileTypes:!0})){if(!c.isFile()||!c.name.endsWith(".json"))continue;const n=h.join(a,c.name),m=w(n);if(m.daemonInstallationId!==this.installationId())throw new Error("native event ledger belongs to another daemon installation");m.sessionId===e&&l.unlinkSync(n)}}isSourceDeleted(e,s){return(this.options.deletionStore??new V({rootDir:h.basename(this.rootDir())==="native-fusion"?h.dirname(this.rootDir()):this.rootDir(),daemonInstallationId:this.installationId()})).has(e,s)}observeNativeTitle(e,s,o){const i=o.trim().slice(0,120);if(!i)return!1;const r=this.readSources().sources[b(e,s)];if(!r||this.isSourceDeleted(e,s))return!1;const a=(this.options.listLocalSessions??L)().find(m=>m.sessionId===r.sessionId);if(a?.titleLockedByUser||a?.titleSource==="agent_native"&&a.title===i)return!1;const c=f.createHash("sha256").update(`${e}\0${s}\0${i}`).digest("hex");return(this.options.recordLocalSession??$)({sessionId:r.sessionId,agentType:r.agentType,workDir:a?.workDir??"",agentSessionId:s,title:i,titleSource:"agent_native",titleOriginEventId:`agent-native:${c}`}),!0}recordIndex(e,s){(this.options.recordLocalSession??$)({sessionId:s,agentType:e.agentType,workDir:e.workDir??"",agentSessionId:e.sourceSessionKey,modelId:e.modelId??null,status:e.terminal?"completed":"active",lastActivityAt:new Date(e.ts).toISOString(),lastMessagePreview:e.role==="user"||e.role==="agent"?O(e.payload):void 0,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(!l.existsSync(e))return{schemaVersion:y,daemonInstallationId:this.installationId(),sources:{}};const s=w(e);if(s.schemaVersion!==y||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??H()}canonicalStore(){return this.options.canonicalStore??B()}rootDir(){return this.options.rootDir??v("native-fusion")}sourcesFile(){return h.join(this.rootDir(),"sources.json")}managedEchoStateFile(){return h.join(this.rootDir(),"managed-echo-state.json")}readManagedEchoState(){const e=this.managedEchoStateFile();if(!l.existsSync(e))return{schemaVersion:y,daemonInstallationId:this.installationId(),claims:{},suppressions:{}};const s=w(e);if(s.schemaVersion!==y||s.daemonInstallationId!==this.installationId()||!s.claims||!s.suppressions)throw new Error("invalid managed echo state");for(const o of Object.values(s.claims))_(o);for(const o of Object.values(s.suppressions))C(o);return s}ledgerFile(e,s){const o=f.createHash("sha256").update(`${e}\0${s}`).digest("hex");return h.join(this.rootDir(),"ledger",`${o}.json`)}}function U(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 f.createHash("sha256").update(`${t}\0${e}`).digest("hex")}function W(t,e,s){return`native-session-${f.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function z(t,e,s){return`native-${f.createHash("sha256").update(`${t}\0${e}\0${s}`).digest("hex").slice(0,32)}`}function G(t){return f.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 _(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 C(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 Q(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 i=typeof o.name=="string"?o.name:"",r=/(?:_output|_result)$/.test(i);return JSON.stringify({...s,...o,...r&&typeof s.name=="string"?{name:s.name}:{}})}catch{return e}}function X(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 A(t,120)}function u(t,e){const s=h.dirname(t);l.mkdirSync(s,{recursive:!0,mode:448});const o=`${t}.tmp-${process.pid}-${f.randomBytes(6).toString("hex")}`;let i;try{i=l.openSync(o,"wx",384),l.writeFileSync(i,`${JSON.stringify(e,null,2)}
2
+ `,"utf8"),l.fsyncSync(i),l.closeSync(i),i=void 0,l.renameSync(o,t)}finally{i!==void 0&&l.closeSync(i);try{l.unlinkSync(o)}catch{}}}function w(t){return JSON.parse(l.readFileSync(t,"utf8"))}export{P as MissingUserBoundaryError,ae as NativeFusionLocalRepository,q as NativeSourceDeletedError};
@@ -0,0 +1,19 @@
1
+ export type PersonalSyncLocalCounter = 'canonicalCommits' | 'sourceCursorAdvanced' | 'indexAcknowledged' | 'indexFailed' | 'indexDirtyRetained' | 'historySnapshots' | 'historyPages' | 'historyDeltas' | 'historyBytesServed';
2
+ export declare function recordPersonalSyncLocalMetric(name: PersonalSyncLocalCounter, amount?: number): void;
3
+ export declare function setPersonalSyncLocalSubscriptions(value: number): void;
4
+ export declare function getPersonalSyncLocalMetrics(now?: Date): {
5
+ generatedAt: string;
6
+ processStartedAt: string;
7
+ activeSubscriptions: number;
8
+ counters: {
9
+ [k: string]: number;
10
+ };
11
+ privacy: {
12
+ containsAccountIdentity: boolean;
13
+ containsSessionIdentity: boolean;
14
+ containsMessageBody: boolean;
15
+ containsToolPayload: boolean;
16
+ containsAttachmentBytes: boolean;
17
+ };
18
+ };
19
+ export declare function resetPersonalSyncLocalMetricsForTests(): void;
@@ -0,0 +1 @@
1
+ const n=["canonicalCommits","sourceCursorAdvanced","indexAcknowledged","indexFailed","indexDirtyRetained","historySnapshots","historyPages","historyDeltas","historyBytesServed"],r=new Date;let o=0;const s=Object.fromEntries(n.map(t=>[t,0]));function c(t,e=1){if(!Number.isFinite(e)||e<0)throw new Error("personal sync metric amount must be non-negative");s[t]+=e}function a(t){o=Math.max(0,Math.floor(t))}function i(t=new Date){return{generatedAt:t.toISOString(),processStartedAt:r.toISOString(),activeSubscriptions:o,counters:Object.fromEntries(n.map(e=>[e,s[e]])),privacy:{containsAccountIdentity:!1,containsSessionIdentity:!1,containsMessageBody:!1,containsToolPayload:!1,containsAttachmentBytes:!1}}}function l(){o=0;for(const t of n)s[t]=0}export{i as getPersonalSyncLocalMetrics,c as recordPersonalSyncLocalMetric,l as resetPersonalSyncLocalMetricsForTests,a as setPersonalSyncLocalSubscriptions};
@@ -1 +1 @@
1
- import{SESSION_HISTORY_LIMITS as r}from"@shennian/wire";import{getCanonicalSessionStore as u}from"./store.js";class b{client;store;subscriptions=new Map;unsubscribeStore;constructor(i,s=u()){this.client=i,this.store=s,this.unsubscribeStore=s.subscribe(e=>this.handleMutation(e))}handleOpen(i){const s=i.params;if(a(s.sessionId,s.subscriptionId),this.subscriptions.has(s.subscriptionId))throw new Error("subscription already exists");if([...this.subscriptions.values()].filter(o=>o.sessionId===s.sessionId).length>=r.maxSubscriptionsPerSession)throw new Error("subscription_limit_exceeded");const t={sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:0,lastSentRevision:0,active:!1,buffered:[]};this.subscriptions.set(s.subscriptionId,t);try{const o=this.store.readPage(s.sessionId,{limit:d(s.limit,r.defaultSnapshotMessages),...s.beforeSequence===void 0?{}:{beforeSequence:s.beforeSequence}});t.snapshotRevision=o.bodyRevision,t.lastSentRevision=o.bodyRevision,this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:s.sessionId,subscriptionId:s.subscriptionId,storageMode:"machine_local",snapshotRevision:o.bodyRevision,firstAvailableSeq:o.firstAvailableSeq,lastAvailableSeq:o.lastAvailableSeq,hasOlder:o.hasOlder,messages:o.entries}}),t.active=!0;for(const c of t.buffered)this.publishMutation(t,c);t.buffered=[]}catch(o){throw this.subscriptions.delete(s.subscriptionId),o}}handlePage(i){const s=i.params,e=this.requireSubscription(s.sessionId,s.subscriptionId);let t;try{t=this.store.readPageAtRevision(s.sessionId,{beforeSequence:s.beforeSequence,limit:d(s.limit,r.defaultPageMessages),revision:e.snapshotRevision})}catch{throw new Error("snapshot_compacted")}this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:e.snapshotRevision,firstAvailableSeq:t.firstAvailableSeq,hasOlder:t.hasOlder,messages:t.entries}})}handleClose(i){const s=i.params;a(s.sessionId,s.subscriptionId),this.subscriptions.get(s.subscriptionId)?.sessionId===s.sessionId&&this.subscriptions.delete(s.subscriptionId),this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:s.sessionId,subscriptionId:s.subscriptionId,closed:!0}})}closeAll(){this.subscriptions.clear(),this.unsubscribeStore()}closeSession(i){for(const[s,e]of this.subscriptions.entries())e.sessionId===i&&this.subscriptions.delete(s)}handleMutation(i){for(const s of this.subscriptions.values())s.sessionId===i.sessionId&&(s.active?this.publishMutation(s,i):s.buffered.push(i))}publishMutation(i,s){s.revision<=i.lastSentRevision||(this.client.sendEvent({type:"event",event:"session.body.delta",payload:{sessionId:i.sessionId,subscriptionId:i.subscriptionId,fromRevision:i.lastSentRevision,toRevision:s.revision,mutations:[{...s,subscriptionId:i.subscriptionId}]}}),i.lastSentRevision=s.revision)}requireSubscription(i,s){a(i,s);const e=this.subscriptions.get(s);if(!e||e.sessionId!==i)throw new Error("subscription_not_found");return e}}function a(n,i){if(typeof n!="string"||!n||typeof i!="string"||!i)throw new Error("invalid history subscription identity")}function d(n,i){const s=n??i;if(!Number.isSafeInteger(s)||s<1||s>r.maxPageMessages)throw new Error("invalid history limit");return s}export{b as PersonalHistorySubscriptions};
1
+ import{SESSION_HISTORY_LIMITS as c}from"@shennian/wire";import{getCanonicalSessionStore as l}from"./store.js";import{recordPersonalSyncLocalMetric as n,setPersonalSyncLocalSubscriptions as a}from"../personal-sync/metrics.js";class I{client;store;subscriptions=new Map;unsubscribeStore;constructor(i,s=l()){this.client=i,this.store=s,this.unsubscribeStore=s.subscribe(t=>this.handleMutation(t))}handleOpen(i){const s=i.params;if(d(s.sessionId,s.subscriptionId),this.subscriptions.has(s.subscriptionId))throw new Error("subscription already exists");if([...this.subscriptions.values()].filter(o=>o.sessionId===s.sessionId).length>=c.maxSubscriptionsPerSession)throw new Error("subscription_limit_exceeded");const e={sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:0,lastSentRevision:0,active:!1,buffered:[]};this.subscriptions.set(s.subscriptionId,e),a(this.subscriptions.size);try{const o=this.store.readPage(s.sessionId,{limit:u(s.limit,c.defaultSnapshotMessages),...s.beforeSequence===void 0?{}:{beforeSequence:s.beforeSequence}});e.snapshotRevision=o.bodyRevision,e.lastSentRevision=o.bodyRevision,this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:s.sessionId,subscriptionId:s.subscriptionId,storageMode:"machine_local",snapshotRevision:o.bodyRevision,firstAvailableSeq:o.firstAvailableSeq,lastAvailableSeq:o.lastAvailableSeq,hasOlder:o.hasOlder,messages:o.entries}}),n("historySnapshots"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(o.entries),"utf8")),e.active=!0;for(const p of e.buffered)this.publishMutation(e,p);e.buffered=[]}catch(o){throw this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size),o}}handlePage(i){const s=i.params,t=this.requireSubscription(s.sessionId,s.subscriptionId);let e;try{e=this.store.readPageAtRevision(s.sessionId,{beforeSequence:s.beforeSequence,limit:u(s.limit,c.defaultPageMessages),revision:t.snapshotRevision})}catch{throw new Error("snapshot_compacted")}this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:t.snapshotRevision,firstAvailableSeq:e.firstAvailableSeq,hasOlder:e.hasOlder,messages:e.entries}}),n("historyPages"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(e.entries),"utf8"))}handleClose(i){const s=i.params;d(s.sessionId,s.subscriptionId),this.subscriptions.get(s.subscriptionId)?.sessionId===s.sessionId&&(this.subscriptions.delete(s.subscriptionId),a(this.subscriptions.size)),this.client.sendRes({type:"res",id:i.id,ok:!0,payload:{sessionId:s.sessionId,subscriptionId:s.subscriptionId,closed:!0}})}closeAll(){this.subscriptions.clear(),a(0),this.unsubscribeStore()}closeSession(i){for(const[s,t]of this.subscriptions.entries())t.sessionId===i&&this.subscriptions.delete(s);a(this.subscriptions.size)}handleMutation(i){for(const s of this.subscriptions.values())s.sessionId===i.sessionId&&(s.active?this.publishMutation(s,i):s.buffered.push(i))}publishMutation(i,s){s.revision<=i.lastSentRevision||(this.client.sendEvent({type:"event",event:"session.body.delta",payload:{sessionId:i.sessionId,subscriptionId:i.subscriptionId,fromRevision:i.lastSentRevision,toRevision:s.revision,mutations:[{...s,subscriptionId:i.subscriptionId}]}}),n("historyDeltas"),n("historyBytesServed",Buffer.byteLength(JSON.stringify(s),"utf8")),i.lastSentRevision=s.revision)}requireSubscription(i,s){d(i,s);const t=this.subscriptions.get(s);if(!t||t.sessionId!==i)throw new Error("subscription_not_found");return t}}function d(r,i){if(typeof r!="string"||!r||typeof i!="string"||!i)throw new Error("invalid history subscription identity")}function u(r,i){const s=r??i;if(!Number.isSafeInteger(s)||s<1||s>c.maxPageMessages)throw new Error("invalid history limit");return s}export{I as PersonalHistorySubscriptions};
@@ -1 +1 @@
1
- import a from"node:crypto";import{acknowledgeSessionIndexUpdate as c,applyGeneratedSessionTitle as f,claimSessionTitleGenerationCandidates as l,listPendingSessionIndexUpdates as p}from"./store.js";const i=new WeakMap,r=new WeakSet;function u(n){const s=n,o=i.get(s);if(o)return r.add(s),o;const e=g(n).finally(()=>{i.get(s)===e&&i.delete(s),r.delete(s)&&y(n)});return i.set(s,e),e}async function g(n){const s=p(),o=[];for(const e of s)try{await n.sendBufferedEvent({type:"event",event:"session.index.updated",id:`session-index-${e.daemonInstallationId}-${e.session.id}-${e.session.indexRevision}`,payload:e},6e4),c(e.session.id,e.session.indexRevision)}catch(t){o.push(t)}if(o.length>0)throw new AggregateError(o,"one or more session indexes failed");for(const e of l())try{const t=await n.sendReq({type:"req",id:`session-title-generate-${a.randomUUID()}`,method:"session.title.generate",params:e},15e3);if(!t.ok)continue;const d=t.payload;d?.generated&&f(e.sessionId,d)&&r.add(n)}catch(t){console.warn(`[session-title] generation attempt ended sessionId=${e.sessionId} requestId=${e.requestId}: ${t instanceof Error?t.message:String(t)}`)}}function y(n){u(n).catch(s=>{console.error("[session-index] sync failed; dirty revisions retained for retry",s)})}export{u as publishPendingSessionIndexes,y as requestSessionIndexPublish};
1
+ import c from"node:crypto";import{acknowledgeSessionIndexUpdate as l,applyGeneratedSessionTitle as f,claimSessionTitleGenerationCandidates as p,listPendingSessionIndexUpdates as u}from"./store.js";import{recordPersonalSyncLocalMetric as r}from"../personal-sync/metrics.js";const t=new WeakMap,d=new WeakSet;function g(s){const n=s,o=t.get(n);if(o)return d.add(n),o;const e=y(s).finally(()=>{t.get(n)===e&&t.delete(n),d.delete(n)&&x(s)});return t.set(n,e),e}async function y(s){const n=u(),o=[];for(const e of n)try{await s.sendBufferedEvent({type:"event",event:"session.index.updated",id:`session-index-${e.daemonInstallationId}-${e.session.id}-${e.session.indexRevision}`,payload:e},6e4),l(e.session.id,e.session.indexRevision),r("indexAcknowledged")}catch(i){o.push(i),r("indexFailed"),r("indexDirtyRetained")}if(o.length>0)throw new AggregateError(o,"one or more session indexes failed");for(const e of p())try{const i=await s.sendReq({type:"req",id:`session-title-generate-${c.randomUUID()}`,method:"session.title.generate",params:e},15e3);if(!i.ok)continue;const a=i.payload;a?.generated&&f(e.sessionId,a)&&d.add(s)}catch(i){console.warn(`[session-title] generation attempt ended sessionId=${e.sessionId} requestId=${e.requestId}: ${i instanceof Error?i.message:String(i)}`)}}function x(s){g(s).catch(n=>{console.error("[session-index] sync failed; dirty revisions retained for retry",n)})}export{g as publishPendingSessionIndexes,x as requestSessionIndexPublish};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.3.54",
3
+ "version": "0.3.56",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {