shennian 0.4.10 → 0.4.11

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.
@@ -346,8 +346,8 @@
346
346
  },
347
347
  {
348
348
  "file": "src/index.js",
349
- "beforeBytes": 27137,
350
- "afterBytes": 13804
349
+ "beforeBytes": 27270,
350
+ "afterBytes": 13879
351
351
  },
352
352
  {
353
353
  "file": "src/log-redaction.js",
@@ -496,8 +496,8 @@
496
496
  },
497
497
  {
498
498
  "file": "src/room/activation-service.js",
499
- "beforeBytes": 22403,
500
- "afterBytes": 11182
499
+ "beforeBytes": 23121,
500
+ "afterBytes": 11545
501
501
  },
502
502
  {
503
503
  "file": "src/room/attachment-cache.js",
@@ -706,8 +706,8 @@
706
706
  },
707
707
  {
708
708
  "file": "src/session/manager.js",
709
- "beforeBytes": 31661,
710
- "afterBytes": 16413
709
+ "beforeBytes": 32004,
710
+ "afterBytes": 16673
711
711
  },
712
712
  {
713
713
  "file": "src/session/native-cleanup-outbox.js",
@@ -726,8 +726,8 @@
726
726
  },
727
727
  {
728
728
  "file": "src/session/queue.js",
729
- "beforeBytes": 33537,
730
- "afterBytes": 14902
729
+ "beforeBytes": 35970,
730
+ "afterBytes": 16080
731
731
  },
732
732
  {
733
733
  "file": "src/session/store.js",
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{Command as se}from"commander";import y from"chalk";import ae from"node:os";import $ from"node:fs";import{loadConfig as l,saveConfig as ce,getConfigPath as me,getShennianDir as de,resolveShennianPath as J,normalizeAutoUpgradePolicy as le}from"./config/index.js";import{CliRelayClient as pe}from"./relay/client.js";import{collectKnownLogSecrets as K,installCliLogRedaction as ge,sanitizeCliLogValue as ue}from"./log-redaction.js";import{registerPairCommand as he,runSmartStart as fe}from"./commands/pair.js";import{registerLogoutCommand as Se}from"./commands/logout.js";import{clearDaemonPidIfOwner as j,createDaemonInstanceId as ve,clearDaemonLauncher as W,findRunningDaemonProcessIds as Ie,isRemoteAccessDisabled as Y,prepareRemoteAccessPause as we,stopPreparedRemoteAccessService as ye,isIsolatedDaemonE2eInstance as Re,registerDaemonCommand as Ce,writeDaemonPid as q,writeDaemonLauncher as Q}from"./commands/daemon.js";import{registerRuntimeCommand as ke}from"./commands/runtime.js";import{registerManagerCommand as Ee}from"./commands/manager.js";import{registerUpgradeCommand as De}from"./commands/upgrade.js";import{registerToolsCommand as Te}from"./commands/tools.js";import{registerIntegrationCommand as be}from"./commands/integration.js";import{registerRoomCommand as Ae}from"./commands/room.js";import{SessionManager as Pe}from"./session/manager.js";import{SERVERS as _,regionToUrl as Oe,urlToRegion as Ue}from"./region.js";import{getCurrentVersion as X,handleStartupCrashCheck as Me}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Ne}from"./upgrade/scheduler.js";import{detectAgents as $e}from"./agents/detect.js";import{augmentProcessPath as Z}from"./env-path.js";Z();const ee=X();import{getCachedAgentInfos as _e,resolveAgentInfos as xe}from"./agents/model-registry.js";import{initCliLogReporter as Le,reportLog as R}from"./log-reporter.js";import{isNativeFusionEnabled as Be}from"./native-fusion/config.js";import{NativeSessionFusionService as Ve}from"./native-fusion/service.js";import{startDaemonLogRetention as Ge}from"./daemon-log.js";import{startDaemonIpcServer as He}from"./daemon-ipc/server.js";import{resolveManagedRoomIpcPaths as Fe}from"./daemon-ipc/paths.js";import{RoomSessionRegistry as ze}from"./room/session-registry.js";import{HttpRoomBindingClient as Je}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Ke}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as je}from"./room/host-session-observations.js";import{RoomMirrorSyncService as We}from"./room/mirror-sync-service.js";import{RoomActivationService as Ye}from"./room/activation-service.js";import{RoomActivationAudit as qe}from"./room/activation-audit.js";import{RoomAttachmentCache as Qe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Xe}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Ze}from"./room/managed-context.js";import{createManagedRoomExecutor as eo}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as oo,SESSION_SYNC_ADVERTISED_CAPABILITIES as no}from"@shennian/wire";import{getDaemonInstallationId as to}from"./session/store.js";import{synchronizeMachinePersonalAuthority as ro}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as io,ensurePersonalDataEpoch as so}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as ao,DeletionSyncTerminalError as co}from"./session/deletion-sync-state.js";const oe=de(),mo=J("daemon.pid");function lo(){try{const o=$.readFileSync(mo,"utf-8").trim();if(o.startsWith("{")){const a=JSON.parse(o),m=Number(a.pid);return Number.isInteger(m)&&m>0?m:null}const r=parseInt(o,10);return Number.isInteger(r)&&r>0?r:null}catch{return null}}function po(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function x(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
- `),process.exitCode=1}function go(o){const r=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&r.push(`reason=${o.reason}`),o.error&&r.push(`error=${o.error}`),r.join(" ")}async function ne(o,r=5e3){const a=Date.now();for(;Date.now()-a<r;){try{process.kill(o,0)}catch{return!0}await new Promise(m=>setTimeout(m,100))}try{return process.kill(o,0),!1}catch{return!0}}const s=new se;s.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(ee),s.enablePositionalOptions(),be(s),Ae(s),s.option("--api <url>","Server URL override").option("--name <name>","Machine name",ae.hostname()).action(async o=>{const r=l(),a=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??r.serverUrl??void 0;await fe(a,o.name)}),s.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const r=J("env.json");try{const e=JSON.parse($.readFileSync(r,"utf-8"));for(const[i,n]of Object.entries(e))process.env[i]||(process.env[i]=n);Z()}catch{}ge(K(process.env,l()));const a=Ge();Y()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(a),process.exit(0));const m=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),L=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await ne(e)||(process.kill(e,"SIGKILL"),await ne(e,2e3))};try{const e=lo();if(e&&e!==process.pid)try{process.kill(e,0),m?await L(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const P=Re(process.env,oe)?[]:Ie(process.pid);if(P.length>0)if(m)for(const e of P)try{await L(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${P[0]}), skipping duplicate start`),process.exit(0);const p=ve();q(process.pid,p,{version:ee}),Q(process.pid,void 0,p),process.on("exit",()=>{j(process.pid,p),W(p)}),await Me()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));try{so()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const c=l(),h=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??c.serverUrl??_.cn.url,u=X(),O=$e(),v=O.map(e=>e.type),U=_e(O),f=new ze,C=new je,I=new Ze(f),S=new Je(h),re=new Qe({actionClient:S}),ie=new Xe({actionClient:S});let M=null,g;const B=eo({authority:I,actionClient:S,attachmentCache:re,attachmentUploader:ie,observations:C,getMachineCredentials:()=>({machineToken:l().machineToken}),onPaused:({registryKey:e,sessionId:i})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),I.revokeSession(e),g?.discardPendingManagedRoomMessages(i)}}),k=await He({paths:Fe(),handlers:{"managed-room.execute":e=>B(e)}});let V=!1;const E=()=>{V||(V=!0,k.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",E),process.once("SIGTERM",E),!c.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await k.close(),process.exitCode=1;return}process.removeListener("SIGINT",E),process.removeListener("SIGTERM",E);const G=`${po(h)}/relay/machine`,N=c.machineToken;if(!N)return;try{await ro({serverUrl:h,machineToken:N,machineId:c.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await k.close(),process.exitCode=1;return}c.machineId&&Le(h,c.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${G}... (v${u}) agents: ${v.join(",")}`),R({level:"info",wsEvent:"daemon.start",metadata:{version:u,agents:v}});let D=null,T=null,b=0;const H=le(c.autoUpgrade),d=new pe({serverUrl:G,machineToken:N,cliVersion:u,agentList:v,autoUpgradePolicy:H,sessionSyncProtocolVersion:oo,sessionSyncCapabilities:no,daemonInstallationId:to(),dataEpoch:io,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),R({level:"info",wsEvent:"daemon.connected"}),U.some(n=>n.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:v,agents:U}}),xe(O,{serverUrl:c.serverUrl??h,authToken:c.machineToken??c.accessToken}).then(n=>{JSON.stringify(n)!==JSON.stringify(U)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:v,agents:n}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:n,clearUpgradeAttempt:t})=>{const w=n();t(),w&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${w.from} \u2192 ${w.to}`),d.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:w.from,to:w.to}}))}).catch(()=>{}),A.start(),T?.start();const e=++b,i=async()=>{try{await g.synchronizeSessionDeletions(),e===b&&d.getState()==="connected"&&D?.handleConnected()}catch(n){if(console.error("[session.deletions] startup catch-up failed",n),e===b&&d.getState()==="connected"){if(n instanceof co)return;const t=n instanceof ao?Math.max(1e3,n.retryAfterMs):5e3;setTimeout(()=>{i()},t).unref?.()}}};i()},onDisconnected:e=>{b+=1,T?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${go(e)}`),R({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),R({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),g.handleReq(e)}});D=Be()?new Ve(d):null,T=new We({registry:f,observations:C,actionClient:S,getMachineCredentials:()=>({machineToken:l().machineToken}),onMessages:async(e,i,n,t)=>{await M?.processMessages(e,i,n,t)},onBindingPaused:e=>{I.revokeSession(e.registryKey),g?.discardPendingManagedRoomMessages(e.nianSessionId)}});const A=new Ne({currentVersion:u,machineId:c.machineId??"unknown-machine",serverUrl:c.serverUrl??h,getIdleState:()=>g.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(d,"auto-upgrade",e,{currentVersion:u})}});A.setPolicy(H),g=new Pe(d,D,u,A,new Ke({registry:f,observations:C,bindingClient:S,getMachineCredentials:()=>{const e=l();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${p}`}),{prepare:()=>we(),isDisabled:()=>Y(),shutdownAfterResponse:()=>{setTimeout(()=>{ye(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>I.revokeToken(e),B),M=new Ye({registry:f,observations:C,enqueueConversationBatch:e=>g.enqueueManagedRoomBatch(e),loadContext:async e=>{const i=l().machineToken?.trim(),n=e.binding;if(!i||!n)throw new Error("managed_room_context_unavailable");const t=await S.status({machineToken:i,roomId:n.roomId,bindingId:n.bindingId});return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,i)=>{const n=l().machineToken;if(!n)throw new Error("machine_not_authorized");return I.issue({registryKey:e,machineCredential:n,activationCauseMessageId:i})},audit:new qe}),g.setManagedRoomTurnPreparer(e=>M.prepareConversationTurn(e)),$.mkdirSync(oe,{recursive:!0}),q(process.pid,p,{version:u}),Q(process.pid,void 0,p),d.connect(),process.stdin.resume();let F=!1;const z=async()=>{if(!F){F=!0,console.log(y.gray(`
3
- Disconnecting...`)),R({level:"info",wsEvent:"daemon.stop"}),clearInterval(a);try{await g.cleanup(),A.stop(),D?.stop(),T?.stop(),d.disconnect(),await k.close(),j(process.pid,p),W(p)}finally{process.exit(0)}}};process.on("SIGINT",()=>{z()}),process.on("SIGTERM",()=>{z()})}),s.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>x()),s.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>x()),s.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>x());const te=s.command("config").description("View or modify configuration");te.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${me()}`);const o=l(),r=ue(o,K(process.env,o));if(o.serverUrl){const a=Ue(o.serverUrl);console.log(`Region: ${a} (${_[a].label})`)}console.log(JSON.stringify(r,null,2))}),te.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,r)=>{const a=l();if(o==="server"){r!=="cn"&&r!=="global"&&(console.error(y.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const m=r;a.serverUrl=Oe(m),ce(a),console.log(y.green(`\u2713 Server set to ${_[m].label}`)),console.log(y.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(y.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),he(s),Se(s),Ce(s),ke(s),Ee(s),De(s),Te(s),s.parse();
1
+ import{Command as se}from"commander";import y from"chalk";import ae from"node:os";import $ from"node:fs";import{loadConfig as p,saveConfig as ce,getConfigPath as me,getShennianDir as de,resolveShennianPath as J,normalizeAutoUpgradePolicy as le}from"./config/index.js";import{CliRelayClient as pe}from"./relay/client.js";import{collectKnownLogSecrets as K,installCliLogRedaction as ge,sanitizeCliLogValue as ue}from"./log-redaction.js";import{registerPairCommand as he,runSmartStart as fe}from"./commands/pair.js";import{registerLogoutCommand as Se}from"./commands/logout.js";import{clearDaemonPidIfOwner as j,createDaemonInstanceId as ve,clearDaemonLauncher as W,findRunningDaemonProcessIds as Ie,isRemoteAccessDisabled as Y,prepareRemoteAccessPause as we,stopPreparedRemoteAccessService as ye,isIsolatedDaemonE2eInstance as Re,registerDaemonCommand as Ce,writeDaemonPid as q,writeDaemonLauncher as Q}from"./commands/daemon.js";import{registerRuntimeCommand as ke}from"./commands/runtime.js";import{registerManagerCommand as De}from"./commands/manager.js";import{registerUpgradeCommand as Ee}from"./commands/upgrade.js";import{registerToolsCommand as Te}from"./commands/tools.js";import{registerIntegrationCommand as be}from"./commands/integration.js";import{registerRoomCommand as Ae}from"./commands/room.js";import{SessionManager as Pe}from"./session/manager.js";import{SERVERS as _,regionToUrl as Me,urlToRegion as Oe}from"./region.js";import{getCurrentVersion as X,handleStartupCrashCheck as Ue}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Ne}from"./upgrade/scheduler.js";import{detectAgents as $e}from"./agents/detect.js";import{augmentProcessPath as Z}from"./env-path.js";Z();const ee=X();import{getCachedAgentInfos as _e,resolveAgentInfos as xe}from"./agents/model-registry.js";import{initCliLogReporter as Le,reportLog as R}from"./log-reporter.js";import{isNativeFusionEnabled as Be}from"./native-fusion/config.js";import{NativeSessionFusionService as Ve}from"./native-fusion/service.js";import{startDaemonLogRetention as Ge}from"./daemon-log.js";import{startDaemonIpcServer as He}from"./daemon-ipc/server.js";import{resolveManagedRoomIpcPaths as Fe}from"./daemon-ipc/paths.js";import{RoomSessionRegistry as ze}from"./room/session-registry.js";import{HttpRoomBindingClient as Je}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Ke}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as je}from"./room/host-session-observations.js";import{RoomMirrorSyncService as We}from"./room/mirror-sync-service.js";import{RoomActivationService as Ye}from"./room/activation-service.js";import{RoomActivationAudit as qe}from"./room/activation-audit.js";import{RoomAttachmentCache as Qe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Xe}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Ze}from"./room/managed-context.js";import{createManagedRoomExecutor as eo}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as oo,SESSION_SYNC_ADVERTISED_CAPABILITIES as no}from"@shennian/wire";import{getDaemonInstallationId as to}from"./session/store.js";import{synchronizeMachinePersonalAuthority as ro}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as io,ensurePersonalDataEpoch as so}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as ao,DeletionSyncTerminalError as co}from"./session/deletion-sync-state.js";const oe=de(),mo=J("daemon.pid");function lo(){try{const o=$.readFileSync(mo,"utf-8").trim();if(o.startsWith("{")){const a=JSON.parse(o),m=Number(a.pid);return Number.isInteger(m)&&m>0?m:null}const r=parseInt(o,10);return Number.isInteger(r)&&r>0?r:null}catch{return null}}function po(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function x(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
+ `),process.exitCode=1}function go(o){const r=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&r.push(`reason=${o.reason}`),o.error&&r.push(`error=${o.error}`),r.join(" ")}async function ne(o,r=5e3){const a=Date.now();for(;Date.now()-a<r;){try{process.kill(o,0)}catch{return!0}await new Promise(m=>setTimeout(m,100))}try{return process.kill(o,0),!1}catch{return!0}}const s=new se;s.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(ee),s.enablePositionalOptions(),be(s),Ae(s),s.option("--api <url>","Server URL override").option("--name <name>","Machine name",ae.hostname()).action(async o=>{const r=p(),a=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??r.serverUrl??void 0;await fe(a,o.name)}),s.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const r=J("env.json");try{const e=JSON.parse($.readFileSync(r,"utf-8"));for(const[i,n]of Object.entries(e))process.env[i]||(process.env[i]=n);Z()}catch{}ge(K(process.env,p()));const a=Ge();Y()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(a),process.exit(0));const m=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),L=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await ne(e)||(process.kill(e,"SIGKILL"),await ne(e,2e3))};try{const e=lo();if(e&&e!==process.pid)try{process.kill(e,0),m?await L(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const M=Re(process.env,oe)?[]:Ie(process.pid);if(M.length>0)if(m)for(const e of M)try{await L(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${M[0]}), skipping duplicate start`),process.exit(0);const g=ve();q(process.pid,g,{version:ee}),Q(process.pid,void 0,g),process.on("exit",()=>{j(process.pid,g),W(g)}),await Ue()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));try{so()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const c=p(),h=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??c.serverUrl??_.cn.url,u=X(),O=$e(),v=O.map(e=>e.type),U=_e(O),f=new ze,C=new je,I=new Ze(f),S=new Je(h),re=new Qe({actionClient:S}),ie=new Xe({actionClient:S});let k=null,d;const B=eo({authority:I,actionClient:S,attachmentCache:re,attachmentUploader:ie,observations:C,getMachineCredentials:()=>({machineToken:p().machineToken}),onPaused:({registryKey:e,sessionId:i})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),I.revokeSession(e),d?.discardPendingManagedRoomMessages(i)}}),D=await He({paths:Fe(),handlers:{"managed-room.execute":e=>B(e)}});let V=!1;const E=()=>{V||(V=!0,D.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",E),process.once("SIGTERM",E),!c.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await D.close(),process.exitCode=1;return}process.removeListener("SIGINT",E),process.removeListener("SIGTERM",E);const G=`${po(h)}/relay/machine`,N=c.machineToken;if(!N)return;try{await ro({serverUrl:h,machineToken:N,machineId:c.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await D.close(),process.exitCode=1;return}c.machineId&&Le(h,c.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${G}... (v${u}) agents: ${v.join(",")}`),R({level:"info",wsEvent:"daemon.start",metadata:{version:u,agents:v}});let T=null,b=null,A=0;const H=le(c.autoUpgrade),l=new pe({serverUrl:G,machineToken:N,cliVersion:u,agentList:v,autoUpgradePolicy:H,sessionSyncProtocolVersion:oo,sessionSyncCapabilities:no,daemonInstallationId:to(),dataEpoch:io,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),R({level:"info",wsEvent:"daemon.connected"}),U.some(n=>n.models.length>0)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:v,agents:U}}),xe(O,{serverUrl:c.serverUrl??h,authToken:c.machineToken??c.accessToken}).then(n=>{JSON.stringify(n)!==JSON.stringify(U)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:v,agents:n}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:n,clearUpgradeAttempt:t})=>{const w=n();t(),w&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${w.from} \u2192 ${w.to}`),l.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:w.from,to:w.to}}))}).catch(()=>{}),P.start(),b?.start();const e=++A,i=async()=>{try{await d.synchronizeSessionDeletions(),e===A&&l.getState()==="connected"&&T?.handleConnected()}catch(n){if(console.error("[session.deletions] startup catch-up failed",n),e===A&&l.getState()==="connected"){if(n instanceof co)return;const t=n instanceof ao?Math.max(1e3,n.retryAfterMs):5e3;setTimeout(()=>{i()},t).unref?.()}}};i()},onDisconnected:e=>{A+=1,b?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${go(e)}`),R({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),R({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),d.handleReq(e)}});T=Be()?new Ve(l):null,b=new We({registry:f,observations:C,actionClient:S,getMachineCredentials:()=>({machineToken:p().machineToken}),onMessages:async(e,i,n,t)=>{await k?.processMessages(e,i,n,t)},onBindingPaused:e=>{I.revokeSession(e.registryKey),d?.discardPendingManagedRoomMessages(e.nianSessionId)}});const P=new Ne({currentVersion:u,machineId:c.machineId??"unknown-machine",serverUrl:c.serverUrl??h,getIdleState:()=>d.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(l,"auto-upgrade",e,{currentVersion:u})}});P.setPolicy(H),d=new Pe(l,T,u,P,new Ke({registry:f,observations:C,bindingClient:S,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${g}`}),{prepare:()=>we(),isDisabled:()=>Y(),shutdownAfterResponse:()=>{setTimeout(()=>{ye(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>I.revokeToken(e),B),k=new Ye({registry:f,observations:C,enqueueConversationBatch:e=>d.enqueueManagedRoomBatch(e),loadContext:async e=>{const i=p().machineToken?.trim(),n=e.binding;if(!i||!n)throw new Error("managed_room_context_unavailable");const t=await S.status({machineToken:i,roomId:n.roomId,bindingId:n.bindingId});return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,i)=>{const n=p().machineToken;if(!n)throw new Error("machine_not_authorized");return I.issue({registryKey:e,machineCredential:n,activationCauseMessageId:i})},audit:new qe}),d.setManagedRoomTurnPreparer(e=>k.prepareConversationTurn(e)),d.setManagedAgentDirectTurnPreparer(e=>k.prepareDirectConversationTurn(e)),$.mkdirSync(oe,{recursive:!0}),q(process.pid,g,{version:u}),Q(process.pid,void 0,g),l.connect(),process.stdin.resume();let F=!1;const z=async()=>{if(!F){F=!0,console.log(y.gray(`
3
+ Disconnecting...`)),R({level:"info",wsEvent:"daemon.stop"}),clearInterval(a);try{await d.cleanup(),P.stop(),T?.stop(),b?.stop(),l.disconnect(),await D.close(),j(process.pid,g),W(g)}finally{process.exit(0)}}};process.on("SIGINT",()=>{z()}),process.on("SIGTERM",()=>{z()})}),s.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>x()),s.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>x()),s.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>x());const te=s.command("config").description("View or modify configuration");te.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${me()}`);const o=p(),r=ue(o,K(process.env,o));if(o.serverUrl){const a=Oe(o.serverUrl);console.log(`Region: ${a} (${_[a].label})`)}console.log(JSON.stringify(r,null,2))}),te.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,r)=>{const a=p();if(o==="server"){r!=="cn"&&r!=="global"&&(console.error(y.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const m=r;a.serverUrl=Me(m),ce(a),console.log(y.green(`\u2713 Server set to ${_[m].label}`)),console.log(y.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(y.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),he(s),Se(s),Ce(s),ke(s),De(s),Ee(s),Te(s),s.parse();
@@ -49,6 +49,8 @@ export declare class RoomActivationService {
49
49
  });
50
50
  processMessages(roomId: string, messages: RoomMessageView[], registrations: RoomSessionRegistration[], context?: RoomActivationContext): Promise<void>;
51
51
  prepareConversationTurn(contexts: ManagedRoomQueueContext[]): Promise<PreparedManagedRoomTurn>;
52
+ prepareDirectConversationTurn(sessionId: string): Promise<PreparedManagedRoomTurn | null>;
53
+ private prepareManagedTurn;
52
54
  private audit;
53
55
  }
54
56
  export declare function resolveManagedAgentPolicy(input: {
@@ -1,8 +1,8 @@
1
- import{isAvailableBuiltinAgentType as A}from"@shennian/wire";import{getBuiltinAgentVersion as k,resolveBuiltinCommand as P}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as S,resolveCustomRuntimeExecutableFingerprint as K}from"../agents/custom-managed-runtime.js";import{loadConfig as B}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as $}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as x}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as q}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as T}from"./activation-policy.js";import{composeManagedRoomPrompt as W}from"./managed-prompt-composer.js";const D=4,R=6e4,E=6,N=5*6e4;class oe{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,s,t,a={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],c=[],g=new Map,u=new Set,m=[...s].sort((n,r)=>G(n.sequence,r.sequence));for(const n of t){const r=this.deps.registry.getByRegistryKey(n.registryKey);!r?.binding||r.binding.roomId!==e||r.binding.status!=="active"&&r.binding.status!=="paused"&&r.binding.activationQueue.length}for(const n of t){const r=this.deps.registry.getByRegistryKey(n.registryKey);if(!r?.binding||r.binding.roomId!==e||r.binding.activationQueue.length===0&&!r.binding.activeActivation||(u.add(r.registryKey),r.binding.status!=="active"||r.binding.activationMode==="paused")||r.binding.activationQueue.length===0)continue;const l=this.deps.observations.getBySource(r);if(!l?.workspacePath||l.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(r.registryKey,a);for(const o of r.binding.activationQueue)d.push({registration:r,input:{params:{sessionId:r.nianSessionId,text:o.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:y(r.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:l.workspacePath,agentSessionId:r.sourceSessionKey,queueMessageId:`room-activation-${r.binding.bindingId}-${o.causeMessageId}`,clientMessageId:`room-activation-${r.binding.bindingId}-${o.causeMessageId}`},context:{registryKey:r.registryKey,causeMessageId:o.causeMessageId,rootCauseMessageId:o.rootCauseMessageId,sequence:o.sequence,hopCount:o.hopCount}}})}for(const n of m){const r=new Set(T(n,t).map(l=>l.registration.registryKey));for(const l of t){const o=this.deps.registry.getByRegistryKey(l.registryKey);if(!o?.binding||o.binding.roomId!==e||o.binding.status!=="active"&&o.binding.status!=="paused"||BigInt(n.sequence)<=BigInt(o.binding.lastActivatedSequence))continue;const f=g.get(o.registryKey)??[...o.binding.mentionContext??[]];if(g.set(o.registryKey,f),r.has(o.registryKey)){const p=(n.activationCause?.hopCount??0)+(n.author.principalType==="agent"?1:0),I=n.activationCause?.rootCauseMessageId??n.id;if(p>D)this.audit(o,n.id,I,p,"suppressed","max_hops");else{const h=this.deps.registry.claimActivation({registryKey:o.registryKey,windowMs:R,maxActivations:E,pauseMs:N,now:this.deps.now?.()});if(!h.allowed){h.newlyPaused&&this.audit(o,n.id,I,p,"suppressed","rate_limited"),c.push({registryKey:o.registryKey,sequence:n.sequence});continue}const b=this.deps.observations.getBySource(o);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const v=C([n]),w=o.binding.activationMode==="mention_only"?L([...f.map(_=>_.deliveryText),v]):v;this.latestContexts.set(o.registryKey,a),d.push({registration:o,input:{params:{sessionId:o.nianSessionId,text:w,agentType:y(o.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:o.sourceSessionKey,queueMessageId:`room-activation-${o.binding.bindingId}-${n.id}`,clientMessageId:`room-activation-${o.binding.bindingId}-${n.id}`},context:{registryKey:o.registryKey,causeMessageId:n.id,rootCauseMessageId:I,sequence:n.sequence,hopCount:p}}}),o.binding.activationMode==="mention_only"&&g.set(o.registryKey,[])}}else o.binding.activationMode==="mention_only"&&n.kind!=="system"&&n.author.membershipId!==o.binding.agentMembershipId&&(f.some(p=>p.sequence===n.sequence)||f.push({sequence:n.sequence,deliveryText:C([n])}));c.push({registryKey:o.registryKey,sequence:n.sequence})}}await this.deps.enqueueConversationBatch(d.map(n=>n.input));for(const n of u)this.deps.registry.retireLegacyActivationState(n,this.deps.now?.()??new Date);for(const[n,r]of g)this.deps.registry.replaceMentionContext(n,r,this.deps.now?.()??new Date);for(const n of d)this.audit(n.registration,n.input.context.causeMessageId,n.input.context.rootCauseMessageId,n.input.context.hopCount,"queued","conversation_queue");for(const n of c)this.deps.registry.advanceCursor({registryKey:n.registryKey,cursor:"activated",sequence:n.sequence})}async prepareConversationTurn(e){const s=e.at(-1);if(!s)throw new Error("managed_room_queue_context_missing");const t=this.deps.registry.getByRegistryKey(s.registryKey);if(!t?.binding||t.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const a=this.deps.loadContext?await this.deps.loadContext(t):this.latestContexts.get(s.registryKey);if(!a)throw new Error("managed_room_prompt_context_unavailable");const d=a.agentsByIdentityId?.[t.binding.agentIdentityId];if(!d?.permissionPolicy)throw new Error("managed_agent_policy_missing");const c=d.sessionMode==="manager"?"manager":"standard",g=c==="manager"?{...d.managerConfig,defaultWorkerAgentType:d.managerConfig?.defaultWorkerAgentType??y(t.runtimeAdapter),defaultWorkerModelId:d.managerConfig?.defaultWorkerModelId??null}:null,u=this.deps.observations.getBySource(t);if(!u?.workspacePath||u.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const m=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:t,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:u.workspacePath}):await j({registration:t,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:u.workspacePath}),n=Q(a,t,m),r=this.deps.issueManagedRoomContext(t.registryKey,s.causeMessageId);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(r))throw new Error("managed_room_context_unavailable");return this.audit(t,s.causeMessageId,s.rootCauseMessageId,s.hopCount,"activated","conversation_queue_dispatch"),{systemPrompt:n.systemPrompt,sessionMode:c,managerConfig:g,managerDefaultWorkerAgentType:g?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:g?.defaultWorkerModelId??null,roomContextRegistryKey:t.registryKey,managedRoomContextToken:r,managedAgentPolicy:m,managedRoomPromptSnapshot:n.snapshot}}audit(e,s,t,a,d,c){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:s,rootCauseMessageId:t,hopCount:a,outcome:d,reason:c,at:this.deps.now?.()})}}function C(i){const e=i.map(s=>{const t=s.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",a=s.attachments.length>0?`
2
- ${s.attachments.map(F).join(`
3
- `)}`:"";return{speaker:s.author.displayName,body:`${t}${a}`}});return M(e)}function F(i){return`\u9644\u4EF6\uFF1A${`${O(i.name)} \xB7 ${V(i.byteSize)}`}\uFF5C${i.mimeType}\uFF5CattachmentId=${i.attachmentId}`}function O(i){return String(i||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function V(i){const e=Number(i);if(!Number.isFinite(e)||e<0)return`${i} B`;if(e<1024)return`${e} B`;const s=["KB","MB","GB","TB"];let t=e,a=-1;do t/=1024,a+=1;while(t>=1024&&a<s.length-1);const d=t>=100?0:t>=10?1:2;return`${t.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${s[a]}`}function L(i){return M(i.map(z))}function z(i){const e=i.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const s=i.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(s)return{speaker:s[1],body:s[2]};const t=i.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const a=i.match(/^([^\n]+)\n([\s\S]*)$/);return a?{speaker:a[1],body:a[2]}:{speaker:"\u7FA4\u6210\u5458",body:i.trim()}}function M(i){if(i.length===1){const[e]=i;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
1
+ import{isAvailableBuiltinAgentType as A}from"@shennian/wire";import{getBuiltinAgentVersion as k,resolveBuiltinCommand as S}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as P,resolveCustomRuntimeExecutableFingerprint as K}from"../agents/custom-managed-runtime.js";import{loadConfig as B}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as $}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as T}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as x}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as q}from"./activation-policy.js";import{composeManagedRoomPrompt as D}from"./managed-prompt-composer.js";const W=4,E=6e4,R=6,N=5*6e4;class oe{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,i,o,r={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],c=[],u=new Map,m=new Set,g=[...i].sort((n,a)=>G(n.sequence,a.sequence));for(const n of o){const a=this.deps.registry.getByRegistryKey(n.registryKey);!a?.binding||a.binding.roomId!==e||a.binding.status!=="active"&&a.binding.status!=="paused"&&a.binding.activationQueue.length}for(const n of o){const a=this.deps.registry.getByRegistryKey(n.registryKey);if(!a?.binding||a.binding.roomId!==e||a.binding.activationQueue.length===0&&!a.binding.activeActivation||(m.add(a.registryKey),a.binding.status!=="active"||a.binding.activationMode==="paused")||a.binding.activationQueue.length===0)continue;const p=this.deps.observations.getBySource(a);if(!p?.workspacePath||p.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(a.registryKey,r);for(const s of a.binding.activationQueue)d.push({registration:a,input:{params:{sessionId:a.nianSessionId,text:s.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:y(a.runtimeAdapter),sessionMode:r.agentsByIdentityId?.[a.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:r.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:r.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:r.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:p.workspacePath,agentSessionId:a.sourceSessionKey,queueMessageId:`room-activation-${a.binding.bindingId}-${s.causeMessageId}`,clientMessageId:`room-activation-${a.binding.bindingId}-${s.causeMessageId}`},context:{registryKey:a.registryKey,causeMessageId:s.causeMessageId,rootCauseMessageId:s.rootCauseMessageId,sequence:s.sequence,hopCount:s.hopCount}}})}for(const n of g){const a=new Set(q(n,o).map(p=>p.registration.registryKey));for(const p of o){const s=this.deps.registry.getByRegistryKey(p.registryKey);if(!s?.binding||s.binding.roomId!==e||s.binding.status!=="active"&&s.binding.status!=="paused"||BigInt(n.sequence)<=BigInt(s.binding.lastActivatedSequence))continue;const f=u.get(s.registryKey)??[...s.binding.mentionContext??[]];if(u.set(s.registryKey,f),a.has(s.registryKey)){const l=(n.activationCause?.hopCount??0)+(n.author.principalType==="agent"?1:0),I=n.activationCause?.rootCauseMessageId??n.id;if(l>W)this.audit(s,n.id,I,l,"suppressed","max_hops");else{const h=this.deps.registry.claimActivation({registryKey:s.registryKey,windowMs:E,maxActivations:R,pauseMs:N,now:this.deps.now?.()});if(!h.allowed){h.newlyPaused&&this.audit(s,n.id,I,l,"suppressed","rate_limited"),c.push({registryKey:s.registryKey,sequence:n.sequence});continue}const b=this.deps.observations.getBySource(s);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const v=M([n]),C=s.binding.activationMode==="mention_only"?L([...f.map(_=>_.deliveryText),v]):v;this.latestContexts.set(s.registryKey,r),d.push({registration:s,input:{params:{sessionId:s.nianSessionId,text:C,agentType:y(s.runtimeAdapter),sessionMode:r.agentsByIdentityId?.[s.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:r.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:r.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:r.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:s.sourceSessionKey,queueMessageId:`room-activation-${s.binding.bindingId}-${n.id}`,clientMessageId:`room-activation-${s.binding.bindingId}-${n.id}`},context:{registryKey:s.registryKey,causeMessageId:n.id,rootCauseMessageId:I,sequence:n.sequence,hopCount:l}}}),s.binding.activationMode==="mention_only"&&u.set(s.registryKey,[])}}else s.binding.activationMode==="mention_only"&&n.kind!=="system"&&n.author.membershipId!==s.binding.agentMembershipId&&(f.some(l=>l.sequence===n.sequence)||f.push({sequence:n.sequence,deliveryText:M([n])}));c.push({registryKey:s.registryKey,sequence:n.sequence})}}await this.deps.enqueueConversationBatch(d.map(n=>n.input));for(const n of m)this.deps.registry.retireLegacyActivationState(n,this.deps.now?.()??new Date);for(const[n,a]of u)this.deps.registry.replaceMentionContext(n,a,this.deps.now?.()??new Date);for(const n of d)this.audit(n.registration,n.input.context.causeMessageId,n.input.context.rootCauseMessageId,n.input.context.hopCount,"queued","conversation_queue");for(const n of c)this.deps.registry.advanceCursor({registryKey:n.registryKey,cursor:"activated",sequence:n.sequence})}async prepareConversationTurn(e){const i=e.at(-1);if(!i)throw new Error("managed_room_queue_context_missing");const o=this.deps.registry.getByRegistryKey(i.registryKey);if(!o)throw new Error("managed_room_binding_unavailable");const r=await this.prepareManagedTurn(o,i.causeMessageId);return this.audit(o,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"activated","conversation_queue_dispatch"),r}async prepareDirectConversationTurn(e){const i=this.deps.registry.getByNianSessionId(e);return i?this.prepareManagedTurn(i):null}async prepareManagedTurn(e,i){if(!e.binding||e.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const o=this.deps.loadContext?await this.deps.loadContext(e):this.latestContexts.get(e.registryKey);if(!o)throw new Error("managed_room_prompt_context_unavailable");const r=o.agentsByIdentityId?.[e.binding.agentIdentityId];if(!r?.permissionPolicy)throw new Error("managed_agent_policy_missing");const d=r.sessionMode==="manager"?"manager":"standard",c=d==="manager"?{...r.managerConfig,defaultWorkerAgentType:r.managerConfig?.defaultWorkerAgentType??y(e.runtimeAdapter),defaultWorkerModelId:r.managerConfig?.defaultWorkerModelId??null}:null,u=this.deps.observations.getBySource(e);if(!u?.workspacePath||u.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const m=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:e,permissionPolicy:r.permissionPolicy,permissionConfirmation:r.permissionConfirmation,workDir:u.workspacePath}):await j({registration:e,permissionPolicy:r.permissionPolicy,permissionConfirmation:r.permissionConfirmation,workDir:u.workspacePath}),g=Q(o,e,m),n=this.deps.issueManagedRoomContext(e.registryKey,i);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(n))throw new Error("managed_room_context_unavailable");return{agentType:y(e.runtimeAdapter),workDir:m.adapterPolicy.workDir,agentSessionId:e.sourceSessionKey,systemPrompt:g.systemPrompt,sessionMode:d,managerConfig:c,managerDefaultWorkerAgentType:c?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:c?.defaultWorkerModelId??null,roomContextRegistryKey:e.registryKey,managedRoomContextToken:n,managedAgentPolicy:m,managedRoomPromptSnapshot:g.snapshot}}audit(e,i,o,r,d,c){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:i,rootCauseMessageId:o,hopCount:r,outcome:d,reason:c,at:this.deps.now?.()})}}function M(t){const e=t.map(i=>{const o=i.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",r=i.attachments.length>0?`
2
+ ${i.attachments.map(F).join(`
3
+ `)}`:"";return{speaker:i.author.displayName,body:`${o}${r}`}});return w(e)}function F(t){return`\u9644\u4EF6\uFF1A${`${O(t.name)} \xB7 ${V(t.byteSize)}`}\uFF5C${t.mimeType}\uFF5CattachmentId=${t.attachmentId}`}function O(t){return String(t||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function V(t){const e=Number(t);if(!Number.isFinite(e)||e<0)return`${t} B`;if(e<1024)return`${e} B`;const i=["KB","MB","GB","TB"];let o=e,r=-1;do o/=1024,r+=1;while(o>=1024&&r<i.length-1);const d=o>=100?0:o>=10?1:2;return`${o.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${i[r]}`}function L(t){return w(t.map(z))}function z(t){const e=t.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const i=t.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(i)return{speaker:i[1],body:i[2]};const o=t.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(o)return{speaker:o[1],body:o[2]};const r=t.match(/^([^\n]+)\n([\s\S]*)$/);return r?{speaker:r[1],body:r[2]}:{speaker:"\u7FA4\u6210\u5458",body:t.trim()}}function w(t){if(t.length===1){const[e]=t;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
4
4
  ${e.body}`}return`[\u7FA4\u6D88\u606F]
5
- ${i.map(e=>`<${e.speaker}>
5
+ ${t.map(e=>`<${e.speaker}>
6
6
  ${e.body}`).join(`
7
7
 
8
- `)}`}function Q(i,e,s){const t=e.binding?i.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!t)throw new Error("managed_room_prompt_incomplete");return W({runtimeAdapter:y(e.runtimeAdapter),agent:{displayName:t.displayName,description:t.description,systemPrompt:i.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId]},room:{name:i.roomName,activationMode:e.binding.activationMode,members:i.members},runtimePermissions:{requestedPolicy:s.requestedPolicy,enforcement:s.enforcement,resolvedWorkDir:s.adapterPolicy.workDir}})}async function j(i){const e=y(i.registration.runtimeAdapter),s=A(e)?P(e):null,t=e.startsWith("custom:")?B().customAgents?.[e.slice(7)]:void 0;t&&S(e,t);const a=s?k(e,s):t?.caps.version,d=s?$(s):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:t?K(t):void 0},c={agentType:e,runtimeVersion:a,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},g=q(c),u=X({confirmation:i.permissionConfirmation,policy:i.permissionPolicy,runtimeAdapter:e,protection:g}),m=await x({...c,policy:i.permissionPolicy,workDir:i.workDir,hostFullAccessConfirmed:u&&i.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:u&&i.permissionConfirmation?.limitedProtection});if(!m.ok){const n=m.unsupportedCapabilities?.length?`:${m.unsupportedCapabilities.join(",")}`:"";throw new Error(`${m.errorCode}${n}`)}return m.value}function X(i){const{confirmation:e,policy:s,runtimeAdapter:t,protection:a}=i;return!!(e&&e.policyVersion===s.version&&e.runtimeAdapter===t&&e.runtimeVersion===a.runtimeVersion&&e.platform===a.platform&&e.executableFingerprint===a.executableFingerprint&&e.hostFullAccess===(s.fileAccess==="host-full")&&e.limitedProtection===(a.level!=="hard"))}function y(i){return i==="claude_code"?"claude":i}function G(i,e){const s=BigInt(i),t=BigInt(e);return s<t?-1:s>t?1:0}export{oe as RoomActivationService,X as managedAgentPermissionConfirmationMatches,j as resolveManagedAgentPolicy};
8
+ `)}`}function Q(t,e,i){const o=e.binding?t.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!o)throw new Error("managed_room_prompt_incomplete");return D({runtimeAdapter:y(e.runtimeAdapter),agent:{displayName:o.displayName,description:o.description,systemPrompt:t.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId]},room:{name:t.roomName,activationMode:e.binding.activationMode,members:t.members},runtimePermissions:{requestedPolicy:i.requestedPolicy,enforcement:i.enforcement,resolvedWorkDir:i.adapterPolicy.workDir}})}async function j(t){const e=y(t.registration.runtimeAdapter),i=A(e)?S(e):null,o=e.startsWith("custom:")?B().customAgents?.[e.slice(7)]:void 0;o&&P(e,o);const r=i?k(e,i):o?.caps.version,d=i?$(i):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:o?K(o):void 0},c={agentType:e,runtimeVersion:r,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},u=x(c),m=X({confirmation:t.permissionConfirmation,policy:t.permissionPolicy,runtimeAdapter:e,protection:u}),g=await T({...c,policy:t.permissionPolicy,workDir:t.workDir,hostFullAccessConfirmed:m&&t.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:m&&t.permissionConfirmation?.limitedProtection});if(!g.ok){const n=g.unsupportedCapabilities?.length?`:${g.unsupportedCapabilities.join(",")}`:"";throw new Error(`${g.errorCode}${n}`)}return g.value}function X(t){const{confirmation:e,policy:i,runtimeAdapter:o,protection:r}=t;return!!(e&&e.policyVersion===i.version&&e.runtimeAdapter===o&&e.runtimeVersion===r.runtimeVersion&&e.platform===r.platform&&e.executableFingerprint===r.executableFingerprint&&e.hostFullAccess===(i.fileAccess==="host-full")&&e.limitedProtection===(r.level!=="hard"))}function y(t){return t==="claude_code"?"claude":t}function G(t,e){const i=BigInt(t),o=BigInt(e);return i<o?-1:i>o?1:0}export{oe as RoomActivationService,X as managedAgentPermissionConfirmationMatches,j as resolveManagedAgentPolicy};
@@ -42,11 +42,14 @@ export declare class SessionManager {
42
42
  private getRuntime;
43
43
  getUpgradeIdleState(): UpgradeIdleState;
44
44
  setManagedRoomTurnPreparer(prepare: (contexts: ManagedRoomQueueContext[]) => Promise<PreparedManagedRoomTurn>): void;
45
+ setManagedAgentDirectTurnPreparer(prepare: (sessionId: string) => Promise<PreparedManagedRoomTurn | null>): void;
45
46
  enqueueManagedRoomBatch(inputs: Array<{
46
47
  params: ManagedRoomEnqueueParams;
47
48
  context: ManagedRoomQueueContext;
48
49
  }>): Promise<void>;
49
50
  discardPendingManagedRoomMessages(sessionId: string): number;
51
+ private managedChatContext;
52
+ private dispatchManagedChat;
50
53
  private dispatchQueuedChat;
51
54
  synchronizeSessionDeletions(): Promise<void>;
52
55
  private synchronizeSessionDeletionsOnce;
@@ -1 +1 @@
1
- import{SESSION_SYNC_PROTOCOL_VERSION as p,SESSION_SYNC_DATA_EPOCH as f,isAvailableAgentType as k}from"@shennian/wire";import g from"node:crypto";import{getRegisteredAgents as S,unregisterAgent as A}from"../agents/adapter.js";import{loadConfig as T}from"../config/index.js";import{handleUpgradeStart as I,handleUpgradeStatus as C}from"../commands/upgrade.js";import{handleAgentsRefresh as P,handleModelsRefresh as E}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as M,handleAgentConfigGet as x,handleAgentConfigTest as O,handleAgentConfigUpsert as _}from"./handlers/agent-config.js";import{handleChatAbort as F,handleChatSend as v}from"./handlers/chat.js";import{handleSessionRefresh as N}from"./handlers/session-refresh.js";import{handleSessionToolDetail as Q}from"./handlers/tool-detail.js";import{handleSessionTitleSet as z}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as W}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as V}from"./handlers/voice-window.js";import{handleRemoteAccessPause as B}from"./handlers/remote-access.js";import{cleanupPendingTransfers as j,handleFsLs as $,handleFsRead as L,handleFsRename as H,handleFsWrite as Y,handleFsTransferAbort as G,handleFsTransferChunk as K,handleFsTransferFinish as X,handleFsTransferStart as Z,handleFsExportMarkdownPdf as J,handleFsExportMarkdownPdfSetup as q,handleFsArchiveZip as ee}from"./handlers/fs.js";import{handleRegionProbe as se,handleRegionSwitch as te,handleUpgradeSetPolicy as ae}from"./handlers/control.js";import{ManagerRuntimeService as ie,setManagerRuntimeService as b}from"../manager/runtime.js";import{ChatQueueManager as ne,TerminalQueuedChatDispatchError as oe}from"./queue.js";import{createAuthorizedFsRoot as re,createAuthorizedFsRootAsync as ce,resolveAuthorizedPath as le,resolveAuthorizedPathAsync as he,resolveSessionWorkDir as de}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/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as ue}from"../agents/custom.js";import{handleManagedRoomBind as me,handleManagedRoomRestart as pe}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as R,purgeCanonicalSession as fe,purgeSessionRecord as ge}from"./store.js";import{PersonalHistorySubscriptions as ve}from"./history-subscriptions.js";import{sweepAttachmentTransfers as be}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ye}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as Re}from"./tool-detail-store.js";import{readDeletionSyncRevision as we,writeDeletionSyncRevision as w}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as ke,clearDeletionSyncFailure as Se,DeletionSyncRetryWaitError as Ae,DeletionSyncTerminalError as Te,recordDeletionSyncFailure as m}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Ie}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as l}from"../personal-sync/epoch.js";const Ce=50;function Pe(r,e){if(!r||!Number.isSafeInteger(r.baselineRevision)||r.baselineRevision<e||!Number.isSafeInteger(r.latestRevision)||r.latestRevision<r.baselineRevision||!Array.isArray(r.deletions))throw new Error("invalid deletion sync response");let t=r.baselineRevision;for(const s of r.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 a of s.sources)if(!a?.sourceSessionKey||!k(a.agentType))throw new Error("invalid native source deletion command");t=s.deletionRevision}if(t!==r.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as bs}from"../fs/boundary.js";class fs{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;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,a=null,i=null,c=null,n=null,h=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=c,this.managedRoomContextRevoker=n,this.managedRoomExecutor=h,this.managerRuntime=new ie({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o,{propagateErrors:!0})}),this.chatQueue=new ne({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o),dispatchQueuedReq:o=>this.dispatchQueuedChat(o)}),this.historySubscriptions=new ve(this.client),this.nativeCleanupOutbox=new Ie({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(o=>{this.handleExternalTerminal(o)}),this.nativeCleanupOutbox.start(),b(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(o=>{console.error("[session.activity] managed probe failed",o)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const o=new Set([...R().map(d=>d.workDir),...Array.from(this.pendingTransfers.values()).map(d=>d.rootPath)]);for(const d of o)try{be(this.resolvePath(d))}catch(u){console.error("[attachment.cleanup] failed",u instanceof Error?u.message:String(u))}},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),resolveAuthorizedPathAsync:(e,t)=>this.resolveAuthorizedPathAsync(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,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor}}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}}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}async enqueueManagedRoomBatch(e){const t=e.map(s=>{const a=R().find(i=>i.sessionId===s.params.sessionId);return{context:s.context,params:{...s.params,modelId:a?.modelId??s.params.modelId,sessionMode:a?.sessionMode??s.params.sessionMode,managerConfig:a?.managerConfig??s.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??s.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??s.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(t)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}async dispatchQueuedChat(e){const t={value:null},s=new Proxy(this.client,{get:(c,n)=>{if(n==="sendRes")return o=>{t.value=o};const h=Reflect.get(c,n,c);return typeof h=="function"?h.bind(c):h}}),a=e.params,i=a.roomActivation===!0?{source:"room_activation",managedAgentPolicy:a.managedAgentPolicy,managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:void 0;if(await v({...this.getRuntime(),client:s},e,i),t.value?.payload?.deliveryState==="failed")throw new oe(t.value.payload.failureMessage??t.value.error??"queued_chat_dispatch_failed");if(!t.value?.ok)throw new Error(t.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){ke();try{await this.synchronizeSessionDeletionsOnce(),Se()}catch(e){if(e instanceof Ae||e instanceof Te)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 m({code:s?"transport_unavailable":"invalid_deletion_sync_response",retryable:s}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),t=we(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const n=s.rejection;throw n?(m({code:n.code,retryable:n.retryable&&n.requiredAction==="retry"}),new Error(n.code)):(m({code:"unstructured_rejection",retryable:!1}),new Error(s.error??"unstructured_rejection"))}const a=s.payload;l(a),Pe(a,t);let i=a.baselineRevision;i>t&&w(i);for(const n of a.deletions)n.deletionRevision<=i||(await this.applySessionDeletion(n),i=n.deletionRevision,w(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const c=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,deletionRevision:i}});if(!c.ok)throw new Error(c.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new ye({daemonInstallationId:y()});t.recordSession(e);for(const a of e.sources)t.record({...e,...a});const s=this.sessions.get(e.sessionId);if(s){this.revokeManagedRoomToken(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),Re(e.sessionId),fe(e.sessionId),ge(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}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),this.revokeManagedRoomToken(t),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 a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:a,canStop:s.canStop??!0});const i=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:i,runPhase:s.runPhase,canStop:s.canStop??!0},seq:i,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of S())t.startsWith("custom:")&&A(t);const e=T();for(const[t,s]of Object.entries(e.customAgents??{}))ue(t,s)}async handleReq(e,t={}){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":l(e.params),await v(s,e);break;case"chat.enqueue":l(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":l(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":l(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":l(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":l(e.params),await F(s,e);break;case"session.refresh":l(e.params),await N(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.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(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 Q(s,e);break;case"voice.session.body.window":V(s,e);break;case"session.title.set":await z(s,e);break;case"room.agent.bind-managed":await me(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await pe(s,this.managedRoomBinder,e);break;case"fs.ls":await $(s,e);break;case"fs.read":await L(s,e);break;case"fs.write":await Y(s,e);break;case"fs.export.markdown-pdf":await J(s,e);break;case"fs.export.markdown-pdf.setup":await q(s,e);break;case"fs.archive.zip":await ee(s,e);break;case"fs.rename":await H(s,e);break;case"fs.transfer.start":await Z(s,e);break;case"fs.transfer.chunk":await K(s,e);break;case"fs.transfer.finish":await X(s,e);break;case"fs.transfer.abort":await G(s,e);break;case"region.probe":await se(s,e);break;case"region.switch":await te(s,e);break;case"upgrade.start":await I(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await C(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await ae(s,e);break;case"machine.remote-access.pause":await B(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await P(s,e);break;case"models.refresh":await E(s,e);break;case"agent.config.get":await x(s,e);break;case"agent.config.upsert":await _(s,e);break;case"agent.config.clear":await M(s,e);break;case"agent.config.test":await O(s,e);break;case"agent.workspace.validate":await W(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(a){if(t.propagateErrors)throw a;this.client.sendRes({type:"res",id:e.id,ok:!1,error:a instanceof Error?a.message:String(a)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<Ce)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(this.revokeManagedRoomToken(e.session),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 de(e)}resolveAuthorizedPath(e,t){return le(e,re(t))}async resolveAuthorizedPathAsync(e,t){return he(e,await ce(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)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),j(this.getRuntime()),await this.managerRuntime.stop(),b(null)}revokeManagedRoomToken(e){const t=e.managedRoomContextToken;e.managedRoomContextToken=null,t&&this.managedRoomContextRevoker?.(t)}}export{fs as SessionManager,bs as resolveSessionWorkDir};
1
+ import{SESSION_SYNC_PROTOCOL_VERSION as g,SESSION_SYNC_DATA_EPOCH as f,isAvailableAgentType as k}from"@shennian/wire";import v from"node:crypto";import{getRegisteredAgents as S,unregisterAgent as A}from"../agents/adapter.js";import{loadConfig as T}from"../config/index.js";import{handleUpgradeStart as C,handleUpgradeStatus as I}from"../commands/upgrade.js";import{handleAgentsRefresh as P,handleModelsRefresh as M}from"./handlers/agents.js";import{handleAgentCapabilitiesList as E}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as D,handleAgentConfigGet as x,handleAgentConfigTest as O,handleAgentConfigUpsert as _}from"./handlers/agent-config.js";import{handleChatAbort as F,handleChatSend as m}from"./handlers/chat.js";import{handleSessionRefresh as Q}from"./handlers/session-refresh.js";import{handleSessionToolDetail as N}from"./handlers/tool-detail.js";import{handleSessionTitleSet as z}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as W}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as V}from"./handlers/voice-window.js";import{handleRemoteAccessPause as B}from"./handlers/remote-access.js";import{cleanupPendingTransfers as j,handleFsLs as $,handleFsRead as L,handleFsRename as H,handleFsWrite as Y,handleFsTransferAbort as G,handleFsTransferChunk as K,handleFsTransferFinish as X,handleFsTransferStart as Z,handleFsExportMarkdownPdf as J,handleFsExportMarkdownPdfSetup as q,handleFsArchiveZip as ee}from"./handlers/fs.js";import{handleRegionProbe as te,handleRegionSwitch as se,handleUpgradeSetPolicy as ae}from"./handlers/control.js";import{ManagerRuntimeService as ie,setManagerRuntimeService as b}from"../manager/runtime.js";import{ChatQueueManager as ne,TerminalQueuedChatDispatchError as oe}from"./queue.js";import{createAuthorizedFsRoot as re,createAuthorizedFsRootAsync as ce,resolveAuthorizedPath as le,resolveAuthorizedPathAsync as he,resolveSessionWorkDir as de}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/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as ue}from"../agents/custom.js";import{handleManagedRoomBind as me,handleManagedRoomRestart as pe}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as R,purgeCanonicalSession as ge,purgeSessionRecord as fe}from"./store.js";import{PersonalHistorySubscriptions as ve}from"./history-subscriptions.js";import{sweepAttachmentTransfers as be}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ye}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as Re}from"./tool-detail-store.js";import{readDeletionSyncRevision as we,writeDeletionSyncRevision as w}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as ke,clearDeletionSyncFailure as Se,DeletionSyncRetryWaitError as Ae,DeletionSyncTerminalError as Te,recordDeletionSyncFailure as p}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Ce}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as l}from"../personal-sync/epoch.js";const Ie=50;function Pe(r,e){if(!r||!Number.isSafeInteger(r.baselineRevision)||r.baselineRevision<e||!Number.isSafeInteger(r.latestRevision)||r.latestRevision<r.baselineRevision||!Array.isArray(r.deletions))throw new Error("invalid deletion sync response");let s=r.baselineRevision;for(const t of r.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 a of t.sources)if(!a?.sourceSessionKey||!k(a.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==r.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as bt}from"../fs/boundary.js";class gt{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;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,a=null,i=null,h=null,n=null,d=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=h,this.managedRoomContextRevoker=n,this.managedRoomExecutor=d,this.managerRuntime=new ie({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o,{propagateErrors:!0})}),this.chatQueue=new ne({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o),dispatchManagedReq:(o,c)=>this.dispatchManagedChat(o,c),dispatchQueuedReq:(o,c)=>this.dispatchQueuedChat(o,c)}),this.historySubscriptions=new ve(this.client),this.nativeCleanupOutbox=new Ce({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(o=>{this.handleExternalTerminal(o)}),this.nativeCleanupOutbox.start(),b(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(o=>{console.error("[session.activity] managed probe failed",o)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const o=new Set([...R().map(c=>c.workDir),...Array.from(this.pendingTransfers.values()).map(c=>c.rootPath)]);for(const c of o)try{be(this.resolvePath(c))}catch(u){console.error("[attachment.cleanup] failed",u instanceof Error?u.message:String(u))}},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),resolveAuthorizedPathAsync:(e,s)=>this.resolveAuthorizedPathAsync(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,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor}}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}}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}setManagedAgentDirectTurnPreparer(e){this.chatQueue.setManagedAgentDirectTurnPreparer(e)}async enqueueManagedRoomBatch(e){const s=e.map(t=>{const a=R().find(i=>i.sessionId===t.params.sessionId);return{context:t.context,params:{...t.params,modelId:a?.modelId??t.params.modelId,sessionMode:a?.sessionMode??t.params.sessionMode,managerConfig:a?.managerConfig??t.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??t.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??t.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(s)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}managedChatContext(e){return{source:"room_activation",managedAgentPolicy:e.managedAgentPolicy,managedRoomPromptSnapshot:e.managedRoomPromptSnapshot}}async dispatchManagedChat(e,s){await m(this.getRuntime(),e,this.managedChatContext(s))}async dispatchQueuedChat(e,s){const t={value:null},a=new Proxy(this.client,{get:(i,h)=>{if(h==="sendRes")return d=>{t.value=d};const n=Reflect.get(i,h,i);return typeof n=="function"?n.bind(i):n}});if(await m({...this.getRuntime(),client:a},e,s?this.managedChatContext(s):void 0),t.value?.payload?.deliveryState==="failed")throw new oe(t.value.payload.failureMessage??t.value.error??"queued_chat_dispatch_failed");if(!t.value?.ok)throw new Error(t.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){ke();try{await this.synchronizeSessionDeletionsOnce(),Se()}catch(e){if(e instanceof Ae||e instanceof Te)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 p({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),s=we(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${v.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:g,dataEpoch:f,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const n=t.rejection;throw n?(p({code:n.code,retryable:n.retryable&&n.requiredAction==="retry"}),new Error(n.code)):(p({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const a=t.payload;l(a),Pe(a,s);let i=a.baselineRevision;i>s&&w(i);for(const n of a.deletions)n.deletionRevision<=i||(await this.applySessionDeletion(n),i=n.deletionRevision,w(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const h=await this.client.sendReq({type:"req",id:`session-deletions-ack-${v.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:g,dataEpoch:f,daemonInstallationId:e,deletionRevision:i}});if(!h.ok)throw new Error(h.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new ye({daemonInstallationId:y()});s.recordSession(e);for(const a of e.sources)s.record({...e,...a});const t=this.sessions.get(e.sessionId);if(t){this.revokeManagedRoomToken(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),Re(e.sessionId),ge(e.sessionId),fe(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}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),this.revokeManagedRoomToken(s),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 a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:a,canStop:t.canStop??!0});const i=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:i,runPhase:t.runPhase,canStop:t.canStop??!0},seq:i,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of S())s.startsWith("custom:")&&A(s);const e=T();for(const[s,t]of Object.entries(e.customAgents??{}))ue(s,t)}async handleReq(e,s={}){const t=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":l(e.params),await m(t,e);break;case"chat.enqueue":l(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":l(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":l(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":l(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":l(e.params),await F(t,e);break;case"session.refresh":l(e.params),await Q(t,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.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(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 N(t,e);break;case"voice.session.body.window":V(t,e);break;case"session.title.set":await z(t,e);break;case"room.agent.bind-managed":await me(t,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await pe(t,this.managedRoomBinder,e);break;case"fs.ls":await $(t,e);break;case"fs.read":await L(t,e);break;case"fs.write":await Y(t,e);break;case"fs.export.markdown-pdf":await J(t,e);break;case"fs.export.markdown-pdf.setup":await q(t,e);break;case"fs.archive.zip":await ee(t,e);break;case"fs.rename":await H(t,e);break;case"fs.transfer.start":await Z(t,e);break;case"fs.transfer.chunk":await K(t,e);break;case"fs.transfer.finish":await X(t,e);break;case"fs.transfer.abort":await G(t,e);break;case"region.probe":await te(t,e);break;case"region.switch":await se(t,e);break;case"upgrade.start":await C(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await I(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await ae(t,e);break;case"machine.remote-access.pause":await B(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await P(t,e);break;case"models.refresh":await M(t,e);break;case"agent.config.get":await x(t,e);break;case"agent.config.upsert":await _(t,e);break;case"agent.config.clear":await D(t,e);break;case"agent.config.test":await O(t,e);break;case"agent.workspace.validate":await W(t,e);break;case"agent.capabilities.list":await E(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(a){if(s.propagateErrors)throw a;this.client.sendRes({type:"res",id:e.id,ok:!1,error:a instanceof Error?a.message:String(a)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<Ie)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});e&&(this.revokeManagedRoomToken(e.session),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 de(e)}resolveAuthorizedPath(e,s){return le(e,re(s))}async resolveAuthorizedPathAsync(e,s){return he(e,await ce(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)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),j(this.getRuntime()),await this.managerRuntime.stop(),b(null)}revokeManagedRoomToken(e){const s=e.managedRoomContextToken;e.managedRoomContextToken=null,s&&this.managedRoomContextRevoker?.(s)}}export{gt as SessionManager,bt as resolveSessionWorkDir};
@@ -11,6 +11,9 @@ export type ManagedRoomQueueContext = {
11
11
  hopCount: number;
12
12
  };
13
13
  export type PreparedManagedRoomTurn = {
14
+ agentType: AgentType;
15
+ workDir: string;
16
+ agentSessionId: string;
14
17
  systemPrompt: string;
15
18
  sessionMode: SessionMode;
16
19
  managerConfig: ManagerConfig | null;
@@ -30,13 +33,16 @@ export declare class ChatQueueManager {
30
33
  private draining;
31
34
  private inFlightMessageIds;
32
35
  private prepareManagedRoomTurn;
36
+ private prepareManagedAgentDirectTurn;
33
37
  constructor(opts: {
34
38
  getRuntime: () => SessionManagerRuntime;
35
39
  dispatchReq: LocalReqDispatcher;
36
- dispatchQueuedReq: LocalReqDispatcher;
40
+ dispatchManagedReq?: (req: ReqFrame, prepared: PreparedManagedRoomTurn) => Promise<void>;
41
+ dispatchQueuedReq: (req: ReqFrame, prepared?: PreparedManagedRoomTurn) => Promise<void>;
37
42
  });
38
43
  getSnapshot(sessionId: string): ChatQueueSnapshot;
39
44
  setManagedRoomTurnPreparer(prepare: (contexts: ManagedRoomQueueContext[]) => Promise<PreparedManagedRoomTurn>): void;
45
+ setManagedAgentDirectTurnPreparer(prepare: (sessionId: string) => Promise<PreparedManagedRoomTurn | null>): void;
40
46
  enqueueManagedRoomBatch(inputs: Array<{
41
47
  params: ManagedRoomEnqueueParams;
42
48
  context: ManagedRoomQueueContext;
@@ -1,12 +1,12 @@
1
- import h from"node:fs";import{randomUUID as q}from"node:crypto";import{CODEX_DESKTOP_SEND_UNAVAILABLE as z,buildUserMessagePayload as j}from"@shennian/wire";import{isCodexThreadTailInterrupted as K}from"../native-fusion/codex-files.js";import{mergeProjectedSessions as J}from"./projection.js";import{parsePersonalAttachmentReferences as D}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as w,markAttachmentReferences as C}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as T}from"../personal-sync/paths.js";import{getPersonalSyncIdentity as S}from"../personal-sync/epoch.js";import{getDaemonInstallationId as R}from"../personal-sync/identity.js";import{appendMessage as v,getMessageDeliveryStatus as k,listSessionRecords as U,recordSession as b,tombstoneMessage as E,upsertMessage as P}from"./store.js";import{requestSessionIndexPublish as M}from"./index-publisher.js";class V extends Error{constructor(e){super(e),this.name="TerminalQueuedChatDispatchError"}}function G(){return{sessions:{}}}function $(){return new Date().toISOString()}function H(s){return`enqueue-send-${s}-${q()}`}function f(){try{const s=JSON.parse(h.readFileSync(A(),"utf-8"));return{sessions:s.sessions&&typeof s.sessions=="object"?s.sessions:{}}}catch(s){if(s.code==="ENOENT"||s instanceof Error&&s.message.startsWith("ENOENT:"))return G();throw s}}function p(s){const e=T();h.mkdirSync(e,{recursive:!0,mode:448});const n=A(),t=`${n}.tmp-${process.pid}-${q()}`;try{h.writeFileSync(t,`${JSON.stringify(s,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"});const i=h.openSync(t,"r");if(typeof i=="number")try{h.fsyncSync(i)}finally{h.closeSync(i)}h.renameSync(t,n)}finally{try{h.unlinkSync(t)}catch{}}}function A(){return T("outbound-queue.json")}function Q(s){const e=$();return{...S(R()),id:s.queueMessageId||s.clientMessageId||`queue-${q()}`,sessionId:s.sessionId,text:s.text,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,reasoningEffort:s.reasoningEffort??null,clientMessageId:s.clientMessageId??null,attachments:D(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}function x(s){return{id:s.clientMessageId??s.id,sessionId:s.sessionId,role:"user",ts:Date.parse(s.createdAt),payload:j(s.text,s.attachments)}}class he{opts;draining=new Set;inFlightMessageIds=new Map;prepareManagedRoomTurn=null;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{...S(R()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:(f().sessions[e]??[]).map(B)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const n=this.opts.getRuntime(),t=f(),i=new Set,o=new Map;let a=!1;for(const r of e){const d=r.params;if(!d.sessionId||!d.text||!d.agentType||!d.workDir)throw new Error("sessionId, text, agentType and workDir are required");const u=Q({...S(R()),...d});u.origin="room",u.managedRoom=r.context;const c=t.sessions[d.sessionId]??[],l=N(c,u),I=u.clientMessageId??u.id,y=k(d.sessionId,I);if(!l&&y.state==="not_found")c.push(u),t.sessions[d.sessionId]=c,o.set(`${u.sessionId}\0${I}`,u),i.add(d.sessionId),a=!0;else if(l&&y.state==="not_found")o.set(`${l.sessionId}\0${I}`,l),i.add(d.sessionId);else if(l&&y.state==="queued")i.add(d.sessionId);else if(l){const m=c.filter(g=>g.id!==l.id);m.length?t.sessions[d.sessionId]=m:delete t.sessions[d.sessionId],a=!0}}a&&p(t);for(const r of o.values())v(r.sessionId,x(r),"queued"),b({sessionId:r.sessionId,agentType:r.agentType,sessionMode:r.sessionMode,managerConfig:r.managerConfig,workDir:r.workDir,agentSessionId:W(n,r),modelId:r.modelId});if(i.size!==0){M(n.client);for(const r of i)this.logLifecycle("enqueue.persisted",r,{origin:"room",pendingCount:f().sessions[r]?.length??0}),this.broadcast(r),this.drainNext(r)}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if(J(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=n.resolvePath(t.workDir),o=D(t.attachments);o?.length&&w(i,o);const a={text:t.text,attachments:o,localized:!1},r=n.sessions.get(t.sessionId);if(t.agentType==="codex"&&t.agentSessionId&&r?.currentRunId?.startsWith("dispatch:")&&K(t.agentSessionId)){n.client.sendRes({type:"res",id:e.id,ok:!1,error:z});return}const d=await this.isSessionBusy(t.sessionId),u=f(),c=u.sessions[t.sessionId]??[];if(!d&&!c.length){await this.opts.dispatchReq({...e,id:H(e.id),method:"chat.send",params:{...t,text:a.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:a.attachments}});return}const l=Q({...t,text:a.text,attachments:a.attachments}),I=l.clientMessageId??l.id,y=k(t.sessionId,I);if(y.state!=="not_found"){n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:y.state==="queued",queueMessageId:l.id,...y,queue:this.getSnapshot(t.sessionId)}});return}const m=N(c,l);m||(u.sessions[t.sessionId]=[...c,l],p(u));const g=m??l;b({sessionId:g.sessionId,agentType:g.agentType,sessionMode:g.sessionMode,managerConfig:g.managerConfig,workDir:g.workDir,agentSessionId:g.agentSessionId,modelId:g.modelId}),v(g.sessionId,x(g),"queued"),C(n.resolvePath(g.workDir),g.attachments??[]),M(n.client),this.logLifecycle("enqueue.persisted",t.sessionId,{origin:t.origin??"app",queueMessageId:g.id,pendingCount:(f().sessions[t.sessionId]??[]).length}),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:m?.id??l.id,...k(t.sessionId,m?.clientMessageId??m?.id??l.clientMessageId??l.id),queue:this.getSnapshot(t.sessionId),...a.localized?{localizedAttachments:!0}:{}}}),d||this.drainNext(t.sessionId)}async handleGet(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(t.sessionId)}async handleEdit(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const i=f(),o=i.sessions[t.sessionId]??[],a=o.findIndex(u=>u.id===t.queueMessageId);if(a<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}if(o[a].managedRoom){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const r=D(t.attachments),d=n.resolvePath(o[a].workDir);r?.length&&w(d,r),o[a]={...o[a],text:t.text,attachments:r,updatedAt:$()},i.sessions[t.sessionId]=o,p(i),P(t.sessionId,x(o[a]),"queued"),o[a].attachments?.length&&C(d,o[a].attachments??[]),M(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const n=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const i=f(),o=i.sessions[t.sessionId]??[],a=o.find(d=>d.id===t.queueMessageId);if(a?.managedRoom){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const r=o.filter(d=>d.id!==t.queueMessageId);if(r.length===o.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}r.length?i.sessions[t.sessionId]=r:delete i.sessions[t.sessionId],p(i),E(t.sessionId,a?.clientMessageId??a?.id??t.queueMessageId),M(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}purgeSession(e){X(e),this.draining.delete(e)}discardPendingManagedRoomMessages(e){const n=f(),t=n.sessions[e]??[],i=this.inFlightMessageIds.get(e)??new Set,o=t.filter(r=>r.managedRoom&&!i.has(r.id));if(o.length===0)return 0;const a=t.filter(r=>!o.includes(r));a.length?n.sessions[e]=a:delete n.sessions[e],p(n);for(const r of o)E(e,r.clientMessageId??r.id);return M(this.opts.getRuntime().client),this.logLifecycle("managed-room.discarded-paused",e,{discardedCount:o.length,pendingCount:a.length}),this.broadcast(e),o.length}drainIdleQueues(){const e=f();for(const n of Object.keys(e.sessions))this.drainNext(n)}async drainNext(e){if(this.draining.has(e))return;this.draining.add(e);let n=!1,t=[];try{if(await this.isSessionBusy(e)){this.logLifecycle("drain.deferred",e,{pendingCount:f().sessions[e]?.length??0});return}const i=f(),o=i.sessions[e]??[],a=o.filter(u=>{const c=u.clientMessageId??u.id,l=k(e,c).state;return l==="queued"||l==="not_found"});a.length!==o.length&&(a.length?i.sessions[e]=a:delete i.sessions[e],p(i),this.logLifecycle("drain.pruned-terminal",e,{prunedCount:o.length-a.length,pendingCount:a.length}));const r=Y(a);if(r.length===0){this.broadcast(e);return}t=r,this.inFlightMessageIds.set(e,new Set(r.map(u=>u.id))),this.logLifecycle("dispatch.start",e,{batchCount:r.length,queueMessageId:r[0].id,origin:r[0].origin??"app"});for(const u of r)P(e,x(u),"queued");await this.dispatchQueuedMessages(r);const d=this.removeBatch(e,r);this.logLifecycle("dispatch.accepted",e,{batchCount:r.length,queueMessageId:r[0].id,pendingCount:d}),n=d>0,this.broadcast(e)}catch(i){if(i instanceof V){const o=this.removeBatch(e,t);this.logLifecycle("dispatch.terminal-failure",e,{batchCount:t.length,queueMessageId:t[0]?.id,pendingCount:o,errorCode:O(i.message,"terminal_dispatch_failed")},"error"),n=o>0}else this.logLifecycle("dispatch.transient-failure",e,{errorCode:O(i instanceof Error?i.message:String(i),"transient_dispatch_failed")},"error");this.broadcast(e)}finally{this.inFlightMessageIds.delete(e),this.draining.delete(e),n&&queueMicrotask(()=>{this.drainNext(e)})}}removeBatch(e,n){if(n.length===0)return f().sessions[e]?.length??0;const t=f(),i=new Set(n.map(a=>a.id)),o=(t.sessions[e]??[]).filter(a=>!i.has(a.id));return o.length?t.sessions[e]=o:delete t.sessions[e],p(t),o.length}logLifecycle(e,n,t,i="info"){const o=`[chat.queue] ${JSON.stringify({event:e,sessionId:n,...t})}`;i==="error"?console.error(o):console.info(o)}async dispatchQueuedMessages(e){const n=e[0],t=e.flatMap(c=>c.managedRoom?[c.managedRoom]:[]),i=t.length>0?await this.prepareManagedRoomTurn?.(t):void 0;if(t.length>0&&!i)throw new Error("managed_room_turn_preparer_unavailable");const o=t.length>0?i:null,a=o?.sessionMode==="manager"?o.managerConfig:null,r=e.length===1?n.text:Z(e),d=te(e.flatMap(c=>c.attachments??[])),u=t.length>0?W(this.opts.getRuntime(),n):n.agentSessionId??null;await this.opts.dispatchQueuedReq({type:"req",id:`queue-send-${n.id}-${Date.now()}`,method:"chat.send",params:{...S(R()),sessionId:n.sessionId,text:r,agentType:n.agentType,sessionMode:o?.sessionMode??n.sessionMode,managerConfig:o?a:n.managerConfig,workDir:n.workDir,agentSessionId:u,modelId:n.modelId??void 0,systemPrompt:i?.systemPrompt??n.systemPrompt??null,managerDefaultWorkerAgentType:o?o.managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:o?o.managerDefaultWorkerModelId:n.managerDefaultWorkerModelId,reasoningEffort:n.reasoningEffort??void 0,clientMessageId:n.clientMessageId??n.id,attachments:d,externalChannel:n.externalChannel,replyTarget:n.replyTarget,...i??{},roomActivation:t.length>0||void 0,suppressExternalOwnerEcho:t.length>0||void 0,queuedBatchItems:e.map(B),waitForDispatch:!0}})}async isSessionBusy(e){const n=this.opts.getRuntime().sessions.get(e);if(!n)return!1;if(n.currentRunId)return!0;if(!n.adapter.getStatus)return!1;const t=await n.adapter.getStatus().catch(()=>null);return t?.active?(t.runId&&(n.currentRunId=t.runId,n.currentRunPhase=t.runPhase??n.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function W(s,e){if(s.sessions.has(e.sessionId))return null;const t=U().find(i=>i.sessionId===e.sessionId)?.agentSessionId?.trim();return!t||t.startsWith("managed:")?null:t}function X(s){const e=f();e.sessions[s]&&(delete e.sessions[s],p(e))}function N(s,e){const n=_(e);if(n)return s.find(i=>_(i)===n)??null;if(e.origin!=="external")return null;const t=F(e);return s.find(i=>F(i)===t)??null}function B(s){const{origin:e,managedRoom:n,...t}=s;return t}function Y(s){const e=s[0];if(!e)return[];const n=L(e),t=[];for(const i of s){if(L(i)!==n)break;t.push(i)}return t}function L(s){return JSON.stringify({agentType:s.agentType,sessionMode:s.sessionMode??null,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,reasoningEffort:s.reasoningEffort??null,managerConfig:s.managerConfig??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,roomRegistryKey:s.managedRoom?.registryKey??null})}function Z(s){return s.every(e=>e.managedRoom)?`[\u7FA4\u6D88\u606F]
3
- ${s.map(e=>ee(e.text)).filter(Boolean).join(`
1
+ import h from"node:fs";import{randomUUID as q}from"node:crypto";import{CODEX_DESKTOP_SEND_UNAVAILABLE as V,buildUserMessagePayload as G}from"@shennian/wire";import{isCodexThreadTailInterrupted as H}from"../native-fusion/codex-files.js";import{mergeProjectedSessions as X}from"./projection.js";import{parsePersonalAttachmentReferences as C}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as E,markAttachmentReferences as b}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as P}from"../personal-sync/paths.js";import{getPersonalSyncIdentity as S}from"../personal-sync/epoch.js";import{getDaemonInstallationId as R}from"../personal-sync/identity.js";import{appendMessage as $,getMessageDeliveryStatus as w,listSessionRecords as v,recordSession as W,tombstoneMessage as Q,upsertMessage as N}from"./store.js";import{requestSessionIndexPublish as D}from"./index-publisher.js";class Y extends Error{constructor(e){super(e),this.name="TerminalQueuedChatDispatchError"}}function Z(){return{sessions:{}}}function B(){return new Date().toISOString()}function ee(s){return`enqueue-send-${s}-${q()}`}function g(){try{const s=JSON.parse(h.readFileSync(L(),"utf-8"));return{sessions:s.sessions&&typeof s.sessions=="object"?s.sessions:{}}}catch(s){if(s.code==="ENOENT"||s instanceof Error&&s.message.startsWith("ENOENT:"))return Z();throw s}}function y(s){const e=P();h.mkdirSync(e,{recursive:!0,mode:448});const t=L(),n=`${t}.tmp-${process.pid}-${q()}`;try{h.writeFileSync(n,`${JSON.stringify(s,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"});const i=h.openSync(n,"r");if(typeof i=="number")try{h.fsyncSync(i)}finally{h.closeSync(i)}h.renameSync(n,t)}finally{try{h.unlinkSync(n)}catch{}}}function L(){return P("outbound-queue.json")}function F(s){const e=B();return{...S(R()),id:s.queueMessageId||s.clientMessageId||`queue-${q()}`,sessionId:s.sessionId,text:s.text,agentType:s.agentType,sessionMode:s.sessionMode,managerConfig:s.managerConfig,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,reasoningEffort:s.reasoningEffort??null,clientMessageId:s.clientMessageId??null,attachments:C(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}function x(s){return{id:s.clientMessageId??s.id,sessionId:s.sessionId,role:"user",ts:Date.parse(s.createdAt),payload:G(s.text,s.attachments)}}class Me{opts;draining=new Set;inFlightMessageIds=new Map;prepareManagedRoomTurn=null;prepareManagedAgentDirectTurn=null;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{...S(R()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:(g().sessions[e]??[]).map(j)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}setManagedAgentDirectTurnPreparer(e){this.prepareManagedAgentDirectTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const t=this.opts.getRuntime(),n=g(),i=new Set,r=new Map;let o=!1;for(const a of e){const d=a.params;if(!d.sessionId||!d.text||!d.agentType||!d.workDir)throw new Error("sessionId, text, agentType and workDir are required");const u=F({...S(R()),...d});u.origin="room",u.managedRoom=a.context;const p=n.sessions[d.sessionId]??[],c=z(p,u),l=u.clientMessageId??u.id,I=w(d.sessionId,l);if(!c&&I.state==="not_found")p.push(u),n.sessions[d.sessionId]=p,r.set(`${u.sessionId}\0${l}`,u),i.add(d.sessionId),o=!0;else if(c&&I.state==="not_found")r.set(`${c.sessionId}\0${l}`,c),i.add(d.sessionId);else if(c&&I.state==="queued")i.add(d.sessionId);else if(c){const m=p.filter(_=>_.id!==c.id);m.length?n.sessions[d.sessionId]=m:delete n.sessions[d.sessionId],o=!0}}o&&y(n);for(const a of r.values())$(a.sessionId,x(a),"queued"),W({sessionId:a.sessionId,agentType:a.agentType,sessionMode:a.sessionMode,managerConfig:a.managerConfig,workDir:a.workDir,agentSessionId:O(t,a),modelId:a.modelId});if(i.size!==0){D(t.client);for(const a of i)this.logLifecycle("enqueue.persisted",a,{origin:"room",pendingCount:g().sessions[a]?.length??0}),this.broadcast(a),this.drainNext(a)}}async handleEnqueue(e){const t=this.opts.getRuntime(),n=e.params;if(X(n.sessionListProjection),!n.sessionId||!n.text||!n.agentType||!n.workDir){t.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=n.managedAgentDirect?await this.prepareManagedAgentDirectTurn?.(n.sessionId):null;if(n.managedAgentDirect&&!i)throw new Error("managed_agent_context_unavailable");const r=i?v().find(k=>k.sessionId===n.sessionId):null,o=i?{...n,...i,agentType:i.agentType,sessionMode:i.sessionMode,managerConfig:i.managerConfig,workDir:i.workDir,agentSessionId:i.agentSessionId,modelId:r?.modelId??void 0,systemPrompt:i.systemPrompt,managerDefaultWorkerAgentType:i.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:i.managerDefaultWorkerModelId}:n,a=t.resolvePath(o.workDir),d=C(o.attachments);d?.length&&E(a,d);const u={text:o.text,attachments:d,localized:!1},p=t.sessions.get(o.sessionId);if(o.agentType==="codex"&&o.agentSessionId&&p?.currentRunId?.startsWith("dispatch:")&&H(o.agentSessionId)){t.client.sendRes({type:"res",id:e.id,ok:!1,error:V});return}const c=await this.isSessionBusy(o.sessionId),l=g(),I=l.sessions[o.sessionId]??[];if(!c&&!I.length){const k={...e,id:ee(e.id),method:"chat.send",params:{...o,text:u.text,responseId:e.id,clientMessageId:o.clientMessageId??o.queueMessageId,waitForDispatch:!0,attachments:u.attachments}};if(i){if(!this.opts.dispatchManagedReq)throw new Error("managed_agent_dispatch_unavailable");await this.opts.dispatchManagedReq(k,i)}else await this.opts.dispatchReq(k);return}const m=F({...o,text:u.text,attachments:u.attachments});i&&(m.managedAgentDirect=!0);const _=m.clientMessageId??m.id,T=w(o.sessionId,_);if(T.state!=="not_found"){t.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:T.state==="queued",queueMessageId:m.id,...T,queue:this.getSnapshot(o.sessionId)}});return}const M=z(I,m);M||(l.sessions[o.sessionId]=[...I,m],y(l));const f=M??m;W({sessionId:f.sessionId,agentType:f.agentType,sessionMode:f.sessionMode,managerConfig:f.managerConfig,workDir:f.workDir,agentSessionId:f.agentSessionId,modelId:f.modelId}),$(f.sessionId,x(f),"queued"),b(t.resolvePath(f.workDir),f.attachments??[]),D(t.client),this.logLifecycle("enqueue.persisted",o.sessionId,{origin:o.origin??"app",queueMessageId:f.id,pendingCount:(g().sessions[o.sessionId]??[]).length}),this.broadcast(o.sessionId),t.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:M?.id??m.id,...w(o.sessionId,M?.clientMessageId??M?.id??m.clientMessageId??m.id),queue:this.getSnapshot(o.sessionId),...u.localized?{localizedAttachments:!0}:{}}}),c||this.drainNext(o.sessionId)}async handleGet(e){const t=this.opts.getRuntime(),n=e.params;if(!n.sessionId){t.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}t.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(n.sessionId)}}),this.getSnapshot(n.sessionId).busy||this.drainNext(n.sessionId)}async handleEdit(e){const t=this.opts.getRuntime(),n=e.params;if(!n.sessionId||!n.queueMessageId||!n.text){t.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const i=g(),r=i.sessions[n.sessionId]??[],o=r.findIndex(u=>u.id===n.queueMessageId);if(o<0){t.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}if(r[o].managedRoom){t.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const a=C(n.attachments),d=t.resolvePath(r[o].workDir);a?.length&&E(d,a),r[o]={...r[o],text:n.text,attachments:a,updatedAt:B()},i.sessions[n.sessionId]=r,y(i),N(n.sessionId,x(r[o]),"queued"),r[o].attachments?.length&&b(d,r[o].attachments??[]),D(t.client),this.broadcast(n.sessionId),t.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(n.sessionId)}})}async handleDelete(e){const t=this.opts.getRuntime(),n=e.params;if(!n.sessionId||!n.queueMessageId){t.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const i=g(),r=i.sessions[n.sessionId]??[],o=r.find(d=>d.id===n.queueMessageId);if(o?.managedRoom){t.client.sendRes({type:"res",id:e.id,ok:!1,error:"managed_agent_queue_mutation_forbidden"});return}const a=r.filter(d=>d.id!==n.queueMessageId);if(a.length===r.length){t.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?i.sessions[n.sessionId]=a:delete i.sessions[n.sessionId],y(i),Q(n.sessionId,o?.clientMessageId??o?.id??n.queueMessageId),D(t.client),this.broadcast(n.sessionId),t.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(n.sessionId)}})}noteTerminal(e){this.drainNext(e)}purgeSession(e){te(e),this.draining.delete(e)}discardPendingManagedRoomMessages(e){const t=g(),n=t.sessions[e]??[],i=this.inFlightMessageIds.get(e)??new Set,r=n.filter(a=>a.managedRoom&&!i.has(a.id));if(r.length===0)return 0;const o=n.filter(a=>!r.includes(a));o.length?t.sessions[e]=o:delete t.sessions[e],y(t);for(const a of r)Q(e,a.clientMessageId??a.id);return D(this.opts.getRuntime().client),this.logLifecycle("managed-room.discarded-paused",e,{discardedCount:r.length,pendingCount:o.length}),this.broadcast(e),r.length}drainIdleQueues(){const e=g();for(const t of Object.keys(e.sessions))this.drainNext(t)}async drainNext(e){if(this.draining.has(e))return;this.draining.add(e);let t=!1,n=[];try{if(await this.isSessionBusy(e)){this.logLifecycle("drain.deferred",e,{pendingCount:g().sessions[e]?.length??0});return}const i=g(),r=i.sessions[e]??[],o=r.filter(u=>{const p=u.clientMessageId??u.id,c=w(e,p).state;return c==="queued"||c==="not_found"});o.length!==r.length&&(o.length?i.sessions[e]=o:delete i.sessions[e],y(i),this.logLifecycle("drain.pruned-terminal",e,{prunedCount:r.length-o.length,pendingCount:o.length}));const a=ne(o);if(a.length===0){this.broadcast(e);return}n=a,this.inFlightMessageIds.set(e,new Set(a.map(u=>u.id))),this.logLifecycle("dispatch.start",e,{batchCount:a.length,queueMessageId:a[0].id,origin:a[0].origin??"app"});for(const u of a)N(e,x(u),"queued");await this.dispatchQueuedMessages(a);const d=this.removeBatch(e,a);this.logLifecycle("dispatch.accepted",e,{batchCount:a.length,queueMessageId:a[0].id,pendingCount:d}),t=d>0,this.broadcast(e)}catch(i){if(i instanceof Y){const r=this.removeBatch(e,n);this.logLifecycle("dispatch.terminal-failure",e,{batchCount:n.length,queueMessageId:n[0]?.id,pendingCount:r,errorCode:U(i.message,"terminal_dispatch_failed")},"error"),t=r>0}else this.logLifecycle("dispatch.transient-failure",e,{errorCode:U(i instanceof Error?i.message:String(i),"transient_dispatch_failed")},"error");this.broadcast(e)}finally{this.inFlightMessageIds.delete(e),this.draining.delete(e),t&&queueMicrotask(()=>{this.drainNext(e)})}}removeBatch(e,t){if(t.length===0)return g().sessions[e]?.length??0;const n=g(),i=new Set(t.map(o=>o.id)),r=(n.sessions[e]??[]).filter(o=>!i.has(o.id));return r.length?n.sessions[e]=r:delete n.sessions[e],y(n),r.length}logLifecycle(e,t,n,i="info"){const r=`[chat.queue] ${JSON.stringify({event:e,sessionId:t,...n})}`;i==="error"?console.error(r):console.info(r)}async dispatchQueuedMessages(e){const t=e[0],n=e.flatMap(l=>l.managedRoom?[l.managedRoom]:[]),i=e.some(l=>l.managedAgentDirect),r=n.length>0?await this.prepareManagedRoomTurn?.(n):i?await this.prepareManagedAgentDirectTurn?.(t.sessionId):void 0;if(n.length>0&&!r)throw new Error("managed_room_turn_preparer_unavailable");if(i&&!r)throw new Error("managed_agent_context_unavailable");const o=r??null,a=o?.sessionMode==="manager"?o.managerConfig:null,d=e.length===1?t.text:se(e),u=oe(e.flatMap(l=>l.attachments??[])),p=i?r.agentSessionId:n.length>0?O(this.opts.getRuntime(),t):t.agentSessionId??null,c=r?v().find(l=>l.sessionId===t.sessionId):null;await this.opts.dispatchQueuedReq({type:"req",id:`queue-send-${t.id}-${Date.now()}`,method:"chat.send",params:{...S(R()),sessionId:t.sessionId,text:d,agentType:i?r.agentType:t.agentType,sessionMode:o?.sessionMode??t.sessionMode,managerConfig:o?a:t.managerConfig,workDir:i?r.workDir:t.workDir,agentSessionId:p,modelId:c?.modelId??t.modelId??void 0,systemPrompt:r?.systemPrompt??t.systemPrompt??null,managerDefaultWorkerAgentType:o?o.managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:o?o.managerDefaultWorkerModelId:t.managerDefaultWorkerModelId,reasoningEffort:t.reasoningEffort??void 0,clientMessageId:t.clientMessageId??t.id,attachments:u,externalChannel:t.externalChannel,replyTarget:t.replyTarget,roomContextRegistryKey:r?.roomContextRegistryKey,managedRoomContextToken:r?.managedRoomContextToken,suppressExternalOwnerEcho:n.length>0||void 0,queuedBatchItems:e.map(j),waitForDispatch:!0}},r??void 0)}async isSessionBusy(e){const t=this.opts.getRuntime().sessions.get(e);if(!t)return!1;if(t.currentRunId)return!0;if(!t.adapter.getStatus)return!1;const n=await t.adapter.getStatus().catch(()=>null);return n?.active?(n.runId&&(t.currentRunId=n.runId,t.currentRunPhase=n.runPhase??t.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function O(s,e){if(s.sessions.has(e.sessionId))return null;const n=v().find(i=>i.sessionId===e.sessionId)?.agentSessionId?.trim();return!n||n.startsWith("managed:")?null:n}function te(s){const e=g();e.sessions[s]&&(delete e.sessions[s],y(e))}function z(s,e){const t=A(e);if(t)return s.find(i=>A(i)===t)??null;if(e.origin!=="external")return null;const n=J(e);return s.find(i=>J(i)===n)??null}function j(s){const{origin:e,managedRoom:t,managedAgentDirect:n,...i}=s;return i}function ne(s){const e=s[0];if(!e)return[];const t=K(e),n=[];for(const i of s){if(K(i)!==t)break;n.push(i)}return n}function K(s){return JSON.stringify({agentType:s.agentType,sessionMode:s.sessionMode??null,workDir:s.workDir,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,systemPrompt:s.systemPrompt??null,reasoningEffort:s.reasoningEffort??null,managerConfig:s.managerConfig??null,managerDefaultWorkerAgentType:s.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s.managerDefaultWorkerModelId??null,externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,roomRegistryKey:s.managedRoom?.registryKey??null})}function se(s){return s.every(e=>e.managedRoom)?`[\u7FA4\u6D88\u606F]
3
+ ${s.map(e=>ie(e.text)).filter(Boolean).join(`
4
4
 
5
5
  `)}`:`[\u8FDE\u7EED\u7528\u6237\u6D88\u606F]
6
- ${s.map((e,n)=>`<\u6D88\u606F ${n+1}>
6
+ ${s.map((e,t)=>`<\u6D88\u606F ${t+1}>
7
7
  ${e.text.trim()}`).join(`
8
8
 
9
- `)}`}function ee(s){const e=s.trim(),n=e.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);return n?`<${n[1]}>
10
- ${n[2]}`:e.startsWith(`[\u7FA4\u6D88\u606F]
11
- `)?e.slice(6):e}function F(s){const e=_(s);return e?`id:${e}`:["body",ne(s.text),...(s.attachments??[]).map(n=>n.path||`${n.name}:${n.mimeType}`)].join(`
12
- `)}function _(s){const e=typeof s.clientMessageId=="string"?s.clientMessageId.trim():"";if(e)return e;const n=typeof s.id=="string"?s.id.trim():"";return n&&!n.startsWith("queue-")?n:""}function te(s){const e=new Set,n=[];for(const t of s){const i=t.path||`${t.name}:${t.mimeType}`;e.has(i)||(e.add(i),n.push(t))}return n.length?n:void 0}function ne(s){return String(s||"").replace(/\s+/g," ").trim()}function O(s,e){const n=s.toLowerCase();return n.includes("no rollout found")?"missing_rollout":n.includes("managed_room_permission_snapshot_mismatch")?"managed_room_permission_snapshot_mismatch":n.includes("managed_room_prompt_snapshot_mismatch")?"managed_room_prompt_snapshot_mismatch":n.includes("managed_room_turn_preparer_unavailable")?"managed_room_turn_preparer_unavailable":n.includes("timeout")?"timeout":n.includes("connection")?"connection_error":e}export{he as ChatQueueManager,V as TerminalQueuedChatDispatchError,X as purgeQueuedSessionData};
9
+ `)}`}function ie(s){const e=s.trim(),t=e.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);return t?`<${t[1]}>
10
+ ${t[2]}`:e.startsWith(`[\u7FA4\u6D88\u606F]
11
+ `)?e.slice(6):e}function J(s){const e=A(s);return e?`id:${e}`:["body",re(s.text),...(s.attachments??[]).map(t=>t.path||`${t.name}:${t.mimeType}`)].join(`
12
+ `)}function A(s){const e=typeof s.clientMessageId=="string"?s.clientMessageId.trim():"";if(e)return e;const t=typeof s.id=="string"?s.id.trim():"";return t&&!t.startsWith("queue-")?t:""}function oe(s){const e=new Set,t=[];for(const n of s){const i=n.path||`${n.name}:${n.mimeType}`;e.has(i)||(e.add(i),t.push(n))}return t.length?t:void 0}function re(s){return String(s||"").replace(/\s+/g," ").trim()}function U(s,e){const t=s.toLowerCase();return t.includes("no rollout found")?"missing_rollout":t.includes("managed_room_permission_snapshot_mismatch")?"managed_room_permission_snapshot_mismatch":t.includes("managed_room_prompt_snapshot_mismatch")?"managed_room_prompt_snapshot_mismatch":t.includes("managed_room_turn_preparer_unavailable")?"managed_room_turn_preparer_unavailable":t.includes("timeout")?"timeout":t.includes("connection")?"connection_error":e}export{Me as ChatQueueManager,Y as TerminalQueuedChatDispatchError,te as purgeQueuedSessionData};
@@ -300,6 +300,8 @@ export type ChatQueueSnapshot = PersonalSyncIdentity & {
300
300
  export type ChatEnqueueParams = ChatSendParams & {
301
301
  queueMessageId?: string;
302
302
  origin?: 'app' | 'external';
303
+ /** Relay-attested. Clients must not set this field directly. */
304
+ managedAgentDirect?: true;
303
305
  };
304
306
  export type ChatQueueGetParams = PersonalSyncIdentity & {
305
307
  sessionId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {