shennian 0.3.71 → 0.3.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/publish-build-manifest.json +6 -6
- package/dist/src/index.js +2 -2
- package/dist/src/session/history-subscriptions.d.ts +1 -0
- package/dist/src/session/history-subscriptions.js +1 -1
- package/dist/src/session/manager.js +1 -1
- package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
- package/node_modules/@shennian/wire/dist/session-sync.d.ts +29 -1
- package/node_modules/@shennian/wire/dist/session-sync.js +13 -0
- package/package.json +1 -1
|
@@ -276,8 +276,8 @@
|
|
|
276
276
|
},
|
|
277
277
|
{
|
|
278
278
|
"file": "src/index.js",
|
|
279
|
-
"beforeBytes":
|
|
280
|
-
"afterBytes":
|
|
279
|
+
"beforeBytes": 24056,
|
|
280
|
+
"afterBytes": 12460
|
|
281
281
|
},
|
|
282
282
|
{
|
|
283
283
|
"file": "src/log-reporter.js",
|
|
@@ -591,8 +591,8 @@
|
|
|
591
591
|
},
|
|
592
592
|
{
|
|
593
593
|
"file": "src/session/history-subscriptions.js",
|
|
594
|
-
"beforeBytes":
|
|
595
|
-
"afterBytes":
|
|
594
|
+
"beforeBytes": 13799,
|
|
595
|
+
"afterBytes": 6616
|
|
596
596
|
},
|
|
597
597
|
{
|
|
598
598
|
"file": "src/session/index-publisher.js",
|
|
@@ -601,8 +601,8 @@
|
|
|
601
601
|
},
|
|
602
602
|
{
|
|
603
603
|
"file": "src/session/manager.js",
|
|
604
|
-
"beforeBytes":
|
|
605
|
-
"afterBytes":
|
|
604
|
+
"beforeBytes": 28952,
|
|
605
|
+
"afterBytes": 14787
|
|
606
606
|
},
|
|
607
607
|
{
|
|
608
608
|
"file": "src/session/native-cleanup-outbox.js",
|
package/dist/src/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{Command as te}from"commander";import w 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 Se}from"./commands/runtime.js";import{registerManagerCommand as ve}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 Ae}from"./agents/detect.js";import{augmentProcessPath as
|
|
2
|
-
`),process.exitCode=1}function io(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 i=Date.now();for(;Date.now()-i<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(
|
|
1
|
+
import{Command as te}from"commander";import w 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 Se}from"./commands/runtime.js";import{registerManagerCommand as ve}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 Ae}from"./agents/detect.js";import{augmentProcessPath as q}from"./env-path.js";q();const Q=Y();import{getCachedAgentInfos as Oe,resolveAgentInfos as Ue}from"./agents/model-registry.js";import{initCliLogReporter as $e,reportLog as I}from"./log-reporter.js";import{isNativeFusionEnabled as Ne}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 He}from"./room/activation-service.js";import{RoomActivationAudit as Ge}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_ADVERTISED_CAPABILITIES as We}from"@shennian/wire";import{getDaemonInstallationId as Ye}from"./session/store.js";import{synchronizeMachinePersonalAuthority as qe}from"./personal-sync/machine-authority.js";import{getPersonalSyncLocalMetrics as Qe}from"./personal-sync/metrics.js";import{CLI_SESSION_SYNC_DATA_EPOCH as Xe,ensurePersonalDataEpoch as Ze}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as eo,DeletionSyncTerminalError as oo}from"./session/deletion-sync-state.js";const X=ae(),no=J("daemon.pid");function to(){try{const o=b.readFileSync(no,"utf-8").trim();if(o.startsWith("{")){const i=JSON.parse(o),c=Number(i.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 ro(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 io(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 i=Date.now();for(;Date.now()-i<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(),i=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await de(i,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[s,r]of Object.entries(e))process.env[s]||(process.env[s]=r);q()}catch{}const i=Pe();ue()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(i),process.exit(0));const c=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),M=async(e,s)=>{console.log(`[${new Date().toISOString()}] ${s} (PID ${e})`),process.kill(e,"SIGTERM"),await Z(e)||(process.kill(e,"SIGKILL"),await Z(e,2e3))};try{const e=to();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 O=he(process.env,X)?[]:ge(process.pid);if(O.length>0)if(c)for(const e of O)try{await M(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${O[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));try{Ze()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const a=d(),g=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??a.serverUrl??P.cn.url,p=Y(),U=Ae(),h=U.map(e=>e.type),$=Oe(U),y=new Me,R=new Ve,L=new ze(y),f=new Le(g),oe=new Fe({actionClient:f}),ne=new Je({actionClient:f});let x=null;const C=await _e({handlers:{"personal.sync.metrics":()=>Qe(),...je({authority:L,actionClient:f,attachmentCache:oe,attachmentUploader:ne,observations:R,getMachineCredentials:()=>({machineToken:d().machineToken})})}});let V=!1;const k=()=>{V||(V=!0,C.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",k),process.once("SIGTERM",k),!a.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await C.close(),process.exitCode=1;return}process.removeListener("SIGINT",k),process.removeListener("SIGTERM",k);const B=`${ro(g)}/relay/machine`,N=a.machineToken;if(!N)return;try{await qe({serverUrl:g,machineToken:N,machineId:a.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await C.close(),process.exitCode=1;return}a.machineId&&$e(g,a.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${B}... (v${p}) agents: ${h.join(",")}`),I({level:"info",wsEvent:"daemon.start",metadata:{version:p,agents:h}});let D=null,E=null,T=0;const H=ce(a.autoUpgrade),m=new me({serverUrl:B,machineToken:N,cliVersion:p,agentList:h,autoUpgradePolicy:H,sessionSyncProtocolVersion:Ke,sessionSyncCapabilities:We,daemonInstallationId:Ye(),dataEpoch:Xe,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),I({level:"info",wsEvent:"daemon.connected"}),$.some(r=>r.models.length>0)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:h,agents:$}}),Ue(U,{serverUrl:a.serverUrl??g,authToken:a.machineToken??a.accessToken}).then(r=>{JSON.stringify(r)!==JSON.stringify($)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:h,agents:r}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:r,clearUpgradeAttempt:S})=>{const v=r();S(),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(()=>{}),A.start(),E?.start();const e=++T,s=async()=>{try{await u.synchronizeSessionDeletions(),e===T&&m.getState()==="connected"&&D?.handleConnected()}catch(r){if(console.error("[session.deletions] startup catch-up failed",r),e===T&&m.getState()==="connected"){if(r instanceof oo)return;const S=r instanceof eo?Math.max(1e3,r.retryAfterMs):5e3;setTimeout(()=>{s()},S).unref?.()}}};s()},onDisconnected:e=>{T+=1,E?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${io(e)}`),I({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}`),I({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),u.handleReq(e)}});D=Ne()?new be(m):null,E=new Be({registry:y,observations:R,actionClient:f,getMachineCredentials:()=>({machineToken:d().machineToken}),onMessages:async(e,s,r,S)=>{await x?.processMessages(e,s,r,S)}});const A=new Te({currentVersion:p,machineId:a.machineId??"unknown-machine",serverUrl:a.serverUrl??g,getIdleState:()=>u.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:s}=await import("./commands/upgrade.js");await s(m,"auto-upgrade",e,{currentVersion:p})}});A.setPolicy(H);const u=new Ce(m,D,p,A,new xe({registry:y,observations:R,bindingClient:f,getMachineCredentials:()=>{const e=d();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${l}`}));x=new He({registry:y,observations:R,activate:e=>u.activateRoomSession(e),issueManagedRoomContext:e=>{const s=d().machineToken;if(!s)throw new Error("machine_not_authorized");return L.issue({registryKey:e,machineCredential:s})},abort:e=>u.abortRoomSession(e),audit:new Ge}),b.mkdirSync(X,{recursive:!0}),K(process.pid,l,{version:p}),W(process.pid,void 0,l),m.connect(),process.stdin.resume();let G=!1;const F=async()=>{if(!G){G=!0,console.log(w.gray(`
|
|
3
3
|
Disconnecting...`)),I({level:"info",wsEvent:"daemon.stop"}),clearInterval(i);try{await u.cleanup(),A.stop(),D?.stop(),E?.stop(),m.disconnect(),await C.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 i=De(n.serverUrl);console.log(`Region: ${i} (${P[i].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 i=d();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(w.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=n;i.serverUrl=ke(c),ie(i),console.log(w.green(`\u2713 Server set to ${P[c].label}`)),console.log(w.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(w.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),le(t),fe(t),Se(t),ve(t),we(t),Ie(t),t.parse();
|
|
@@ -11,6 +11,7 @@ export declare class PersonalHistorySubscriptions {
|
|
|
11
11
|
private unsubscribeStore;
|
|
12
12
|
constructor(client: Pick<CliRelayClient, 'sendRes' | 'sendEvent'>, store?: CanonicalSessionStore, hasLocalSession?: (sessionId: string) => boolean);
|
|
13
13
|
handleOpen(req: ReqFrame): void;
|
|
14
|
+
handleSync(req: ReqFrame): void;
|
|
14
15
|
handlePage(req: ReqFrame): void;
|
|
15
16
|
handleClose(req: ReqFrame): void;
|
|
16
17
|
closeAll(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SESSION_HISTORY_LIMITS as o}from"@shennian/wire";import{getCanonicalSessionStore as
|
|
1
|
+
import{SESSION_HISTORY_LIMITS as o}from"@shennian/wire";import{getCanonicalSessionStore as v,listSessionRecords as g}from"./store.js";import{recordPersonalSyncLocalMetric as r,setPersonalSyncLocalSubscriptions as l}from"../personal-sync/metrics.js";import{assertPersonalSyncIdentity as c,getPersonalSyncIdentity as u}from"../personal-sync/epoch.js";const R=100,_=o.maxDeltaMutations,A=o.maxMutationBytes-4096,y=o.maxReplicaPageBytes-16384;class L{client;store;hasLocalSession;subscriptions=new Map;unsubscribeStore;constructor(e,s=v(),i=t=>g().some(n=>n.sessionId===t)){this.client=e,this.store=s,this.hasLocalSession=i,this.unsubscribeStore=s.subscribe(t=>this.handleMutation(t))}handleOpen(e){const s=e.params;if(c(s),h(s.sessionId,s.subscriptionId),!this.hasLocalSession(s.sessionId)&&!this.store.getManifest(s.sessionId))throw new Error("history_unavailable");if(this.subscriptions.has(s.subscriptionId))throw new Error("subscription already exists");if([...this.subscriptions.values()].filter(n=>n.sessionId===s.sessionId).length>=o.maxSubscriptionsPerSession){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"subscription_limit_exceeded",rejection:{code:"subscription_limit_exceeded",retryable:!0,requiredAction:"retry"}});return}const t={sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:0,lastSentRevision:0,active:!1,buffered:[],pending:[],pendingBytes:0,flushTimer:null,lastQueuedRevision:0};this.subscriptions.set(s.subscriptionId,t),l(this.subscriptions.size);try{const n=this.store.readPage(s.sessionId,{limit:S(s.limit,o.defaultSnapshotMessages),...s.beforeSequence===void 0?{}:{beforeSequence:s.beforeSequence}});t.snapshotRevision=n.bodyRevision,t.lastSentRevision=n.bodyRevision,t.lastQueuedRevision=n.bodyRevision,this.client.sendRes({type:"res",id:e.id,ok:!0,payload:{...u(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,storageMode:"machine_local",snapshotRevision:n.bodyRevision,firstAvailableSeq:n.firstAvailableSeq,lastAvailableSeq:n.lastAvailableSeq,hasOlder:n.hasOlder,messages:n.entries}}),r("historySnapshots"),r("historyBytesServed",Buffer.byteLength(JSON.stringify(n.entries),"utf8")),t.active=!0;for(const d of t.buffered)this.queueMutation(t,d);t.buffered=[]}catch(n){throw this.subscriptions.delete(s.subscriptionId),l(this.subscriptions.size),n}}handleSync(e){const s=e.params;if(c(s),h(s.sessionId,s.syncId),!Number.isSafeInteger(s.afterRevision)||s.afterRevision<0)throw new Error("invalid history sync revision");if(!this.hasLocalSession(s.sessionId)&&!this.store.getManifest(s.sessionId))throw new Error("history_unavailable");const i=this.store.getManifest(s.sessionId)?.bodyRevision??0;if(s.afterRevision>i)throw new Error("revision_gap");const t=M(s.limit),n=this.store.readDeltas(s.sessionId,s.afterRevision,t),d=[];let f=0;for(const m of n){const b={...m,subscriptionId:s.syncId},p=Buffer.byteLength(JSON.stringify(b),"utf8")+1;if(d.length>0&&f+p>y)break;if(p>y){this.client.sendRes({type:"res",id:e.id,ok:!1,error:"response_too_large"});return}d.push(b),f+=p}const I=d.at(-1)?.revision??s.afterRevision;this.client.sendRes({type:"res",id:e.id,ok:!0,payload:{...u(this.store.daemonInstallationId),sessionId:s.sessionId,syncId:s.syncId,fromRevision:s.afterRevision,toRevision:I,targetRevision:i,hasMore:I<i,mutations:d}}),r("historyDeltas"),r("historyBytesServed",f)}handlePage(e){const s=e.params;c(s);const i=this.requireSubscription(s.sessionId,s.subscriptionId);let t;try{t=this.store.readPageAtRevision(s.sessionId,{beforeSequence:s.beforeSequence,limit:S(s.limit,o.defaultPageMessages),revision:i.snapshotRevision})}catch{throw new Error("snapshot_compacted")}this.client.sendRes({type:"res",id:e.id,ok:!0,payload:{...u(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,snapshotRevision:i.snapshotRevision,firstAvailableSeq:t.firstAvailableSeq,hasOlder:t.hasOlder,messages:t.entries}}),r("historyPages"),r("historyBytesServed",Buffer.byteLength(JSON.stringify(t.entries),"utf8"))}handleClose(e){const s=e.params;c(s),h(s.sessionId,s.subscriptionId);const i=this.subscriptions.get(s.subscriptionId);i?.sessionId===s.sessionId&&(this.flushSubscription(i),this.subscriptions.delete(s.subscriptionId),l(this.subscriptions.size)),this.client.sendRes({type:"res",id:e.id,ok:!0,payload:{...u(this.store.daemonInstallationId),sessionId:s.sessionId,subscriptionId:s.subscriptionId,closed:!0}})}closeAll(){for(const e of this.subscriptions.values())this.flushSubscription(e);this.subscriptions.clear(),l(0),this.unsubscribeStore()}closeSession(e){for(const[s,i]of this.subscriptions.entries())i.sessionId===e&&(this.flushSubscription(i),this.subscriptions.delete(s));l(this.subscriptions.size)}handleMutation(e){for(const s of this.subscriptions.values())s.sessionId===e.sessionId&&(s.active?this.queueMutation(s,e):s.buffered.push(e))}queueMutation(e,s){if(s.revision<=e.lastQueuedRevision)return;const i=Buffer.byteLength(JSON.stringify({...s,subscriptionId:e.subscriptionId}),"utf8")+1;if(e.pending.length>0&&e.pendingBytes+i>A&&this.flushSubscription(e),e.pending.push(s),e.pendingBytes+=i,e.lastQueuedRevision=s.revision,e.pending.length>=_){this.flushSubscription(e);return}e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushSubscription(e),R))}flushSubscription(e){if(e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null),e.pending.length===0)return;const s=e.pending.filter(t=>t.revision>e.lastSentRevision);if(e.pending=[],e.pendingBytes=0,s.length===0)return;const i=s[s.length-1].revision;this.client.sendEvent({type:"event",event:"session.body.delta",payload:{...u(this.store.daemonInstallationId),sessionId:e.sessionId,subscriptionId:e.subscriptionId,fromRevision:e.lastSentRevision,toRevision:i,mutations:s.map(t=>({...t,subscriptionId:e.subscriptionId}))}}),r("historyDeltas"),r("historyBytesServed",Buffer.byteLength(JSON.stringify(s),"utf8")),e.lastSentRevision=i}requireSubscription(e,s){h(e,s);const i=this.subscriptions.get(s);if(!i||i.sessionId!==e)throw new Error("subscription_not_found");return i}}function h(a,e){if(typeof a!="string"||!a||typeof e!="string"||!e)throw new Error("invalid history subscription identity")}function S(a,e){const s=a??e;if(!Number.isSafeInteger(s)||s<1||s>o.maxPageMessages)throw new Error("invalid history limit");return s}function M(a){const e=a??o.maxDeltaMutations;if(!Number.isSafeInteger(e)||e<1||e>o.maxDeltaMutations)throw new Error("invalid history sync limit");return e}export{_ as PERSONAL_HISTORY_DELTA_BATCH_MAX_MUTATIONS,R as PERSONAL_HISTORY_DELTA_BATCH_WINDOW_MS,L as PersonalHistorySubscriptions};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SESSION_SYNC_PROTOCOL_VERSION as u,SESSION_SYNC_DATA_EPOCH as m,isAvailableAgentType as S}from"@shennian/wire";import h from"node:crypto";import{getRegisteredAgents as R,unregisterAgent as k}from"../agents/adapter.js";import{loadConfig as I}from"../config/index.js";import{handleUpgradeStart as A,handleUpgradeStatus as T}from"../commands/upgrade.js";import{handleAgentsRefresh as C,handleModelsRefresh as P}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as E,handleAgentConfigGet as x,handleAgentConfigTest as O,handleAgentConfigUpsert as F}from"./handlers/agent-config.js";import{handleChatAbort as p,handleChatSend as f}from"./handlers/chat.js";import{handleSessionRefresh as M}from"./handlers/session-refresh.js";import{handleSessionToolDetail as _}from"./handlers/tool-detail.js";import{handleSessionTitleSet as N}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as Q}from"./handlers/voice-window.js";import{cleanupPendingTransfers as z,handleFsLs as V,handleFsRead as W,handleFsRename as $,handleFsWrite as j,handleFsTransferAbort as B,handleFsTransferChunk as L,handleFsTransferFinish as H,handleFsTransferStart as Y,handleFsExportMarkdownPdf as G,handleFsExportMarkdownPdfSetup as K,handleFsArchiveZip as X}from"./handlers/fs.js";import{handleRegionProbe as Z,handleRegionSwitch as J,handleUpgradeSetPolicy as q}from"./handlers/control.js";import{ManagerRuntimeService as ee,setManagerRuntimeService as g}from"../manager/runtime.js";import{ChatQueueManager as te}from"./queue.js";import{createAuthorizedFsRoot as se,resolveAuthorizedPath as ie,resolveSessionWorkDir as ae}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as ne}from"../agents/custom.js";import{handleManagedRoomBind as re,handleManagedRoomRestart as oe}from"./handlers/room-managed.js";import{getDaemonInstallationId as v,listSessionRecords as b,purgeCanonicalSession as ce,purgeSessionRecord as le}from"./store.js";import{PersonalHistorySubscriptions as he}from"./history-subscriptions.js";import{sweepAttachmentTransfers as de}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ue}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as me}from"./tool-detail-store.js";import{readDeletionSyncRevision as pe,writeDeletionSyncRevision as y}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as fe,clearDeletionSyncFailure as ge,DeletionSyncRetryWaitError as ve,DeletionSyncTerminalError as be,recordDeletionSyncFailure as d}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as ye}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as c}from"../personal-sync/epoch.js";const we=50;function Se(o,e){if(!o||!Number.isSafeInteger(o.baselineRevision)||o.baselineRevision<e||!Number.isSafeInteger(o.latestRevision)||o.latestRevision<o.baselineRevision||!Array.isArray(o.deletions))throw new Error("invalid deletion sync response");let t=o.baselineRevision;for(const s of o.deletions){if(!s?.sessionId||!Number.isSafeInteger(s.deletionRevision)||s.deletionRevision!==t+1||!Array.isArray(s.sources)||Number.isNaN(Date.parse(s.deletedAt))||s.nativeCleanupPolicy!==void 0&&s.nativeCleanupPolicy!=="none"&&s.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const i of s.sources)if(!i?.sourceSessionKey||!S(i.agentType))throw new Error("invalid native source deletion command");t=s.deletionRevision}if(t!==o.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as ct}from"../fs/boundary.js";class nt{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,t=null,s,i=null,a=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=i,this.managedRoomBinder=a,this.managerRuntime=new ee({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.chatQueue=new te({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.historySubscriptions=new he(this.client),this.nativeCleanupOutbox=new ye({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(n=>{this.handleExternalTerminal(n)}),this.nativeCleanupOutbox.start(),g(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...b().map(r=>r.workDir),...Array.from(this.pendingTransfers.values()).map(r=>r.rootPath)]);for(const r of n)try{de(this.resolvePath(r))}catch(l){console.error("[attachment.cleanup] failed",l instanceof Error?l.message:String(l))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async synchronizeSessionDeletions(){fe();try{await this.synchronizeSessionDeletionsOnce(),ge()}catch(e){if(e instanceof ve||e instanceof be)throw e;const t=e instanceof Error?e.message:String(e),s=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(t);throw d({code:s?"transport_unavailable":"invalid_deletion_sync_response",retryable:s}),e}}async synchronizeSessionDeletionsOnce(){const e=v(),t=pe(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${h.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const r=s.rejection;throw r?(d({code:r.code,retryable:r.retryable&&r.requiredAction==="retry"}),new Error(r.code)):(d({code:"unstructured_rejection",retryable:!1}),new Error(s.error??"unstructured_rejection"))}const i=s.payload;c(i),Se(i,t);let a=i.baselineRevision;a>t&&y(a);for(const r of i.deletions)r.deletionRevision<=a||(await this.applySessionDeletion(r),a=r.deletionRevision,y(a));if(a!==i.latestRevision)throw new Error(`deletion revision gap: applied ${a}, latest ${i.latestRevision}`);if(a===0)return;const n=await this.client.sendReq({type:"req",id:`session-deletions-ack-${h.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,deletionRevision:a}});if(!n.ok)throw new Error(n.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new ue({daemonInstallationId:v()});t.recordSession(e);for(const i of e.sources)t.record({...e,...i});const s=this.sessions.get(e.sessionId);if(s){try{await s.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),me(e.sessionId),ce(e.sessionId),le(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}async activateRoomSession(e){const t=b().find(n=>n.sessionId===e.sessionId),s=t?.agentType===e.agentType?t.modelId??void 0:void 0,i={value:null},a=new Proxy(this.client,{get:(n,r)=>{if(r==="sendRes")return w=>{i.value=w};const l=Reflect.get(n,r,n);return typeof l=="function"?l.bind(n):l}});return await f({...this.getRuntime(),client:a},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:s,sessionMode:t?.sessionMode??(t?.agentType==="manager"?"manager":"standard"),managerConfig:t?.managerConfig??null,managerDefaultWorkerAgentType:t?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:t?.managerDefaultWorkerModelId??null,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:i.value?.ok===!0&&i.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const t=new Proxy(this.client,{get:(s,i)=>{if(i==="sendRes")return()=>{};const a=Reflect.get(s,i,s);return typeof a=="function"?a.bind(s):a}});await p({...this.getRuntime(),client:t},{type:"req",id:`room-activation-abort-${h.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async handleExternalTerminal(e){const t=this.sessions.get(e);t&&(this.sessions.delete(e),t.heartbeatTimer&&clearInterval(t.heartbeatTimer),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const i=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:i,canStop:s.canStop??!0});const a=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:a,runPhase:s.runPhase,canStop:s.canStop??!0},seq:a,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of R())t.startsWith("custom:")&&k(t);const e=I();for(const[t,s]of Object.entries(e.customAgents??{}))ne(t,s)}async handleReq(e){const t=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":c(e.params),await f(t,e);break;case"chat.enqueue":c(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":c(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":c(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":c(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":c(e.params),await p(t,e);break;case"session.refresh":c(e.params),await M(t,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":U(t,e);break;case"session.tool.detail":await _(t,e);break;case"voice.session.body.window":Q(t,e);break;case"session.title.set":await N(t,e);break;case"room.agent.bind-managed":await re(t,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await oe(t,this.managedRoomBinder,e);break;case"fs.ls":await V(t,e);break;case"fs.read":await W(t,e);break;case"fs.write":await j(t,e);break;case"fs.export.markdown-pdf":await G(t,e);break;case"fs.export.markdown-pdf.setup":await K(t,e);break;case"fs.archive.zip":await X(t,e);break;case"fs.rename":await $(t,e);break;case"fs.transfer.start":await Y(t,e);break;case"fs.transfer.chunk":await L(t,e);break;case"fs.transfer.finish":await H(t,e);break;case"fs.transfer.abort":await B(t,e);break;case"region.probe":await Z(t,e);break;case"region.switch":await J(t,e);break;case"upgrade.start":await A(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await T(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await q(t,e);break;case"agents.refresh":await C(t,e);break;case"models.refresh":await P(t,e);break;case"agent.config.get":await x(t,e);break;case"agent.config.upsert":await F(t,e);break;case"agent.config.clear":await E(t,e);break;case"agent.config.test":await O(t,e);break;case"agent.capabilities.list":await D(t,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(s){this.client.sendRes({type:"res",id:e.id,ok:!1,error:s instanceof Error?s.message:String(s)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<we)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return ae(e)}resolveAuthorizedPath(e,t){return ie(e,se(t))}async cleanup(){this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),z(this.getRuntime()),await this.managerRuntime.stop(),g(null)}}export{nt as SessionManager,ct as resolveSessionWorkDir};
|
|
1
|
+
import{SESSION_SYNC_PROTOCOL_VERSION as u,SESSION_SYNC_DATA_EPOCH as m,isAvailableAgentType as S}from"@shennian/wire";import h from"node:crypto";import{getRegisteredAgents as R,unregisterAgent as k}from"../agents/adapter.js";import{loadConfig as I}from"../config/index.js";import{handleUpgradeStart as A,handleUpgradeStatus as T}from"../commands/upgrade.js";import{handleAgentsRefresh as C,handleModelsRefresh as P}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as E,handleAgentConfigGet as x,handleAgentConfigTest as O,handleAgentConfigUpsert as F}from"./handlers/agent-config.js";import{handleChatAbort as p,handleChatSend as f}from"./handlers/chat.js";import{handleSessionRefresh as M}from"./handlers/session-refresh.js";import{handleSessionToolDetail as _}from"./handlers/tool-detail.js";import{handleSessionTitleSet as N}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as Q}from"./handlers/voice-window.js";import{cleanupPendingTransfers as z,handleFsLs as V,handleFsRead as W,handleFsRename as $,handleFsWrite as j,handleFsTransferAbort as B,handleFsTransferChunk as L,handleFsTransferFinish as H,handleFsTransferStart as Y,handleFsExportMarkdownPdf as G,handleFsExportMarkdownPdfSetup as K,handleFsArchiveZip as X}from"./handlers/fs.js";import{handleRegionProbe as Z,handleRegionSwitch as J,handleUpgradeSetPolicy as q}from"./handlers/control.js";import{ManagerRuntimeService as ee,setManagerRuntimeService as g}from"../manager/runtime.js";import{ChatQueueManager as se}from"./queue.js";import{createAuthorizedFsRoot as te,resolveAuthorizedPath as ie,resolveSessionWorkDir as ae}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as ne}from"../agents/custom.js";import{handleManagedRoomBind as re,handleManagedRoomRestart as oe}from"./handlers/room-managed.js";import{getDaemonInstallationId as v,listSessionRecords as b,purgeCanonicalSession as ce,purgeSessionRecord as le}from"./store.js";import{PersonalHistorySubscriptions as he}from"./history-subscriptions.js";import{sweepAttachmentTransfers as de}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ue}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as me}from"./tool-detail-store.js";import{readDeletionSyncRevision as pe,writeDeletionSyncRevision as y}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as fe,clearDeletionSyncFailure as ge,DeletionSyncRetryWaitError as ve,DeletionSyncTerminalError as be,recordDeletionSyncFailure as d}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as ye}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as c}from"../personal-sync/epoch.js";const we=50;function Se(o,e){if(!o||!Number.isSafeInteger(o.baselineRevision)||o.baselineRevision<e||!Number.isSafeInteger(o.latestRevision)||o.latestRevision<o.baselineRevision||!Array.isArray(o.deletions))throw new Error("invalid deletion sync response");let s=o.baselineRevision;for(const t of o.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const i of t.sources)if(!i?.sourceSessionKey||!S(i.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==o.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as cs}from"../fs/boundary.js";class ns{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,s=null,t,i=null,a=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=i,this.managedRoomBinder=a,this.managerRuntime=new ee({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.chatQueue=new se({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.historySubscriptions=new he(this.client),this.nativeCleanupOutbox=new ye({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(n=>{this.handleExternalTerminal(n)}),this.nativeCleanupOutbox.start(),g(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...b().map(r=>r.workDir),...Array.from(this.pendingTransfers.values()).map(r=>r.rootPath)]);for(const r of n)try{de(this.resolvePath(r))}catch(l){console.error("[attachment.cleanup] failed",l instanceof Error?l.message:String(l))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,s)=>this.resolveAuthorizedPath(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async synchronizeSessionDeletions(){fe();try{await this.synchronizeSessionDeletionsOnce(),ge()}catch(e){if(e instanceof ve||e instanceof be)throw e;const s=e instanceof Error?e.message:String(e),t=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(s);throw d({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=v(),s=pe(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${h.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const r=t.rejection;throw r?(d({code:r.code,retryable:r.retryable&&r.requiredAction==="retry"}),new Error(r.code)):(d({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const i=t.payload;c(i),Se(i,s);let a=i.baselineRevision;a>s&&y(a);for(const r of i.deletions)r.deletionRevision<=a||(await this.applySessionDeletion(r),a=r.deletionRevision,y(a));if(a!==i.latestRevision)throw new Error(`deletion revision gap: applied ${a}, latest ${i.latestRevision}`);if(a===0)return;const n=await this.client.sendReq({type:"req",id:`session-deletions-ack-${h.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,deletionRevision:a}});if(!n.ok)throw new Error(n.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new ue({daemonInstallationId:v()});s.recordSession(e);for(const i of e.sources)s.record({...e,...i});const t=this.sessions.get(e.sessionId);if(t){try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),me(e.sessionId),ce(e.sessionId),le(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}async activateRoomSession(e){const s=b().find(n=>n.sessionId===e.sessionId),t=s?.agentType===e.agentType?s.modelId??void 0:void 0,i={value:null},a=new Proxy(this.client,{get:(n,r)=>{if(r==="sendRes")return w=>{i.value=w};const l=Reflect.get(n,r,n);return typeof l=="function"?l.bind(n):l}});return await f({...this.getRuntime(),client:a},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:t,sessionMode:s?.sessionMode??(s?.agentType==="manager"?"manager":"standard"),managerConfig:s?.managerConfig??null,managerDefaultWorkerAgentType:s?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s?.managerDefaultWorkerModelId??null,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:i.value?.ok===!0&&i.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const s=new Proxy(this.client,{get:(t,i)=>{if(i==="sendRes")return()=>{};const a=Reflect.get(t,i,t);return typeof a=="function"?a.bind(t):a}});await p({...this.getRuntime(),client:s},{type:"req",id:`room-activation-abort-${h.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,s){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:s}})}async handleExternalTerminal(e){const s=this.sessions.get(e);s&&(this.sessions.delete(e),s.heartbeatTimer&&clearInterval(s.heartbeatTimer),s.adapter.removeAllListeners(),await s.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const i=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:i,canStop:t.canStop??!0});const a=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:a,runPhase:t.runPhase,canStop:t.canStop??!0},seq:a,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of R())s.startsWith("custom:")&&k(s);const e=I();for(const[s,t]of Object.entries(e.customAgents??{}))ne(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":c(e.params),await f(s,e);break;case"chat.enqueue":c(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":c(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":c(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":c(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":c(e.params),await p(s,e);break;case"session.refresh":c(e.params),await M(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":U(s,e);break;case"session.tool.detail":await _(s,e);break;case"voice.session.body.window":Q(s,e);break;case"session.title.set":await N(s,e);break;case"room.agent.bind-managed":await re(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await oe(s,this.managedRoomBinder,e);break;case"fs.ls":await V(s,e);break;case"fs.read":await W(s,e);break;case"fs.write":await j(s,e);break;case"fs.export.markdown-pdf":await G(s,e);break;case"fs.export.markdown-pdf.setup":await K(s,e);break;case"fs.archive.zip":await X(s,e);break;case"fs.rename":await $(s,e);break;case"fs.transfer.start":await Y(s,e);break;case"fs.transfer.chunk":await L(s,e);break;case"fs.transfer.finish":await H(s,e);break;case"fs.transfer.abort":await B(s,e);break;case"region.probe":await Z(s,e);break;case"region.switch":await J(s,e);break;case"upgrade.start":await A(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await T(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await q(s,e);break;case"agents.refresh":await C(s,e);break;case"models.refresh":await P(s,e);break;case"agent.config.get":await x(s,e);break;case"agent.config.upsert":await F(s,e);break;case"agent.config.clear":await E(s,e);break;case"agent.config.test":await O(s,e);break;case"agent.capabilities.list":await D(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<we)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return ae(e)}resolveAuthorizedPath(e,s){return ie(e,te(s))}async cleanup(){this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),z(this.getRuntime()),await this.managerRuntime.stop(),g(null)}}export{ns as SessionManager,cs as resolveSessionWorkDir};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type ReqMethod = 'chat.send' | 'chat.enqueue' | 'chat.queue.get' | 'chat.queue.edit' | 'chat.queue.delete' | 'chat.abort' | 'session.resume' | 'session.history' | 'session.history.open' | 'session.history.page' | 'session.history.close' | 'session.index.update' | 'session.deletions.sync' | 'session.deletions.ack' | 'session.message.status' | 'voice.session.body.window' | 'session.refresh' | 'session.tool.detail' | 'session.title.set' | 'session.title.generate' | 'fs.ls' | 'fs.read' | 'fs.write' | 'fs.export.markdown-pdf' | 'fs.export.markdown-pdf.setup' | 'fs.archive.zip' | 'fs.transfer.start' | 'fs.transfer.chunk' | 'fs.transfer.finish' | 'fs.transfer.abort' | 'fs.rename' | 'skill.list' | 'skill.install' | 'skill.doctor' | 'skill.setup' | 'skill.use' | 'region.probe' | 'region.switch' | 'upgrade.start' | 'upgrade.status' | 'upgrade.set-policy' | 'agents.refresh' | 'models.refresh' | 'agent.config.get' | 'agent.config.upsert' | 'agent.config.clear' | 'agent.config.test' | 'agent.capabilities.list' | 'manager.channel.get' | 'manager.channel.upsert' | 'session.wechat-rpa.channel.get' | 'session.wechat-rpa.channel.upsert' | 'session.wechat-rpa.channel.sync' | 'session.wechat-rpa.outbound.cancel' | 'manager.wechat-rpa.channel.get' | 'manager.wechat-rpa.channel.upsert' | 'manager.wechat-rpa.channel.sync' | 'manager.wechat-rpa.outbound.cancel' | 'wechat-rpa.helper.runtime' | 'wecom.send' | 'external.send' | 'room.sync' | 'room.send' | 'room.bind' | 'room.agent.bind-managed' | 'room.agent.restart-managed' | 'room.unbind';
|
|
1
|
+
export type ReqMethod = 'chat.send' | 'chat.enqueue' | 'chat.queue.get' | 'chat.queue.edit' | 'chat.queue.delete' | 'chat.abort' | 'session.resume' | 'session.history' | 'session.history.open' | 'session.history.sync' | 'session.history.page' | 'session.history.close' | 'session.index.update' | 'session.deletions.sync' | 'session.deletions.ack' | 'session.message.status' | 'voice.session.body.window' | 'session.refresh' | 'session.tool.detail' | 'session.title.set' | 'session.title.generate' | 'fs.ls' | 'fs.read' | 'fs.write' | 'fs.export.markdown-pdf' | 'fs.export.markdown-pdf.setup' | 'fs.archive.zip' | 'fs.transfer.start' | 'fs.transfer.chunk' | 'fs.transfer.finish' | 'fs.transfer.abort' | 'fs.rename' | 'skill.list' | 'skill.install' | 'skill.doctor' | 'skill.setup' | 'skill.use' | 'region.probe' | 'region.switch' | 'upgrade.start' | 'upgrade.status' | 'upgrade.set-policy' | 'agents.refresh' | 'models.refresh' | 'agent.config.get' | 'agent.config.upsert' | 'agent.config.clear' | 'agent.config.test' | 'agent.capabilities.list' | 'manager.channel.get' | 'manager.channel.upsert' | 'session.wechat-rpa.channel.get' | 'session.wechat-rpa.channel.upsert' | 'session.wechat-rpa.channel.sync' | 'session.wechat-rpa.outbound.cancel' | 'manager.wechat-rpa.channel.get' | 'manager.wechat-rpa.channel.upsert' | 'manager.wechat-rpa.channel.sync' | 'manager.wechat-rpa.outbound.cancel' | 'wechat-rpa.helper.runtime' | 'wecom.send' | 'external.send' | 'room.sync' | 'room.send' | 'room.bind' | 'room.agent.bind-managed' | 'room.agent.restart-managed' | 'room.unbind';
|
|
@@ -16,6 +16,13 @@ export declare const SESSION_SYNC_RELEASE_GATE: {
|
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
18
|
export declare const SESSION_SYNC_REQUIRED_CAPABILITIES: readonly ["session_index_v1", "session_body_revision_v1", "session_history_subscription_v1", "local_canonical_v1", "session_title_authority_v1", "session_title_seed_v1", "session_tool_detail_v1", "voice_session_body_relay_v1", "native_source_tombstone_v1", "session_durable_send_v1", "daemon_installation_identity_v1", "session_user_boundary_v1", "session_terminal_outbox_v1", "session_list_preview_v1", "session_attention_v1", "session_data_epoch_v2", "session_structured_rejection_v2", "machine_personal_authority_v2"];
|
|
19
|
+
/**
|
|
20
|
+
* Additive capabilities are advertised by new daemons but are not part of the
|
|
21
|
+
* epoch-wide minimum gate. This lets the App roll out background replication
|
|
22
|
+
* while old daemons keep serving the compatible visible-session snapshot path.
|
|
23
|
+
*/
|
|
24
|
+
export declare const SESSION_SYNC_ADDITIVE_CAPABILITIES: readonly ["session_body_replica_v1"];
|
|
25
|
+
export declare const SESSION_SYNC_ADVERTISED_CAPABILITIES: readonly ["session_index_v1", "session_body_revision_v1", "session_history_subscription_v1", "local_canonical_v1", "session_title_authority_v1", "session_title_seed_v1", "session_tool_detail_v1", "voice_session_body_relay_v1", "native_source_tombstone_v1", "session_durable_send_v1", "daemon_installation_identity_v1", "session_user_boundary_v1", "session_terminal_outbox_v1", "session_list_preview_v1", "session_attention_v1", "session_data_epoch_v2", "session_structured_rejection_v2", "machine_personal_authority_v2", "session_body_replica_v1"];
|
|
19
26
|
export declare const SESSION_SYNC_EVENT_NAMES: readonly ["session.index.updated", "session.body.delta"];
|
|
20
27
|
export declare const SESSION_HISTORY_LIMITS: {
|
|
21
28
|
readonly defaultSnapshotMessages: 100;
|
|
@@ -24,10 +31,11 @@ export declare const SESSION_HISTORY_LIMITS: {
|
|
|
24
31
|
readonly maxPageMessages: 500;
|
|
25
32
|
readonly maxDeltaMutations: 200;
|
|
26
33
|
readonly maxMutationBytes: number;
|
|
34
|
+
readonly maxReplicaPageBytes: number;
|
|
27
35
|
readonly maxSubscriptionsPerClient: 4;
|
|
28
36
|
readonly maxSubscriptionsPerSession: 8;
|
|
29
37
|
};
|
|
30
|
-
export type SessionSyncCapability = (typeof
|
|
38
|
+
export type SessionSyncCapability = (typeof SESSION_SYNC_ADVERTISED_CAPABILITIES)[number];
|
|
31
39
|
export type SessionSyncEventName = (typeof SESSION_SYNC_EVENT_NAMES)[number];
|
|
32
40
|
export type SessionBodyStorageMode = 'machine_local';
|
|
33
41
|
export type NativeCleanupPolicy = 'none' | 'auto';
|
|
@@ -159,6 +167,12 @@ export type SessionHistoryOpenParams = PersonalSyncIdentity & {
|
|
|
159
167
|
limit?: number;
|
|
160
168
|
beforeSequence?: number;
|
|
161
169
|
};
|
|
170
|
+
export type SessionHistorySyncParams = PersonalSyncIdentity & {
|
|
171
|
+
sessionId: string;
|
|
172
|
+
syncId: string;
|
|
173
|
+
afterRevision: number;
|
|
174
|
+
limit?: number;
|
|
175
|
+
};
|
|
162
176
|
export type SessionHistoryMessage = {
|
|
163
177
|
sequence: number;
|
|
164
178
|
revision: number;
|
|
@@ -229,6 +243,20 @@ export type SessionBodyDeltaEvent = PersonalSyncIdentity & {
|
|
|
229
243
|
toRevision: number;
|
|
230
244
|
mutations: SessionBodyMutation[];
|
|
231
245
|
};
|
|
246
|
+
/**
|
|
247
|
+
* One-shot, resumable body replication page. Unlike history.open this does not
|
|
248
|
+
* create a live subscription. Mutations remain contiguous from fromRevision;
|
|
249
|
+
* targetRevision is the daemon high-water captured for this page.
|
|
250
|
+
*/
|
|
251
|
+
export type SessionHistorySyncPage = PersonalSyncIdentity & {
|
|
252
|
+
sessionId: string;
|
|
253
|
+
syncId: string;
|
|
254
|
+
fromRevision: number;
|
|
255
|
+
toRevision: number;
|
|
256
|
+
targetRevision: number;
|
|
257
|
+
hasMore: boolean;
|
|
258
|
+
mutations: SessionBodyMutation[];
|
|
259
|
+
};
|
|
232
260
|
/**
|
|
233
261
|
* Machine-authored personal-session projection. Ownership, machine display
|
|
234
262
|
* fields and viewer permissions are server authority and therefore never
|
|
@@ -30,6 +30,18 @@ export const SESSION_SYNC_REQUIRED_CAPABILITIES = [
|
|
|
30
30
|
'session_structured_rejection_v2',
|
|
31
31
|
'machine_personal_authority_v2',
|
|
32
32
|
];
|
|
33
|
+
/**
|
|
34
|
+
* Additive capabilities are advertised by new daemons but are not part of the
|
|
35
|
+
* epoch-wide minimum gate. This lets the App roll out background replication
|
|
36
|
+
* while old daemons keep serving the compatible visible-session snapshot path.
|
|
37
|
+
*/
|
|
38
|
+
export const SESSION_SYNC_ADDITIVE_CAPABILITIES = [
|
|
39
|
+
'session_body_replica_v1',
|
|
40
|
+
];
|
|
41
|
+
export const SESSION_SYNC_ADVERTISED_CAPABILITIES = [
|
|
42
|
+
...SESSION_SYNC_REQUIRED_CAPABILITIES,
|
|
43
|
+
...SESSION_SYNC_ADDITIVE_CAPABILITIES,
|
|
44
|
+
];
|
|
33
45
|
export const SESSION_SYNC_EVENT_NAMES = [
|
|
34
46
|
'session.index.updated',
|
|
35
47
|
'session.body.delta',
|
|
@@ -41,6 +53,7 @@ export const SESSION_HISTORY_LIMITS = {
|
|
|
41
53
|
maxPageMessages: 500,
|
|
42
54
|
maxDeltaMutations: 200,
|
|
43
55
|
maxMutationBytes: 256 * 1024,
|
|
56
|
+
maxReplicaPageBytes: 4 * 1024 * 1024,
|
|
44
57
|
maxSubscriptionsPerClient: 4,
|
|
45
58
|
maxSubscriptionsPerSession: 8,
|
|
46
59
|
};
|