shennian 0.3.72 → 0.3.73

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.
@@ -276,8 +276,8 @@
276
276
  },
277
277
  {
278
278
  "file": "src/index.js",
279
- "beforeBytes": 24056,
280
- "afterBytes": 12460
279
+ "beforeBytes": 25214,
280
+ "afterBytes": 12983
281
281
  },
282
282
  {
283
283
  "file": "src/log-reporter.js",
@@ -421,8 +421,8 @@
421
421
  },
422
422
  {
423
423
  "file": "src/room/activation-service.js",
424
- "beforeBytes": 15508,
425
- "afterBytes": 7310
424
+ "beforeBytes": 14897,
425
+ "afterBytes": 6867
426
426
  },
427
427
  {
428
428
  "file": "src/room/attachment-cache.js",
@@ -456,8 +456,8 @@
456
456
  },
457
457
  {
458
458
  "file": "src/room/managed-context.js",
459
- "beforeBytes": 4442,
460
- "afterBytes": 2478
459
+ "beforeBytes": 4512,
460
+ "afterBytes": 2530
461
461
  },
462
462
  {
463
463
  "file": "src/room/managed-prompt-composer.js",
@@ -471,8 +471,8 @@
471
471
  },
472
472
  {
473
473
  "file": "src/room/managed-room-ipc.js",
474
- "beforeBytes": 7797,
475
- "afterBytes": 3832
474
+ "beforeBytes": 7658,
475
+ "afterBytes": 3766
476
476
  },
477
477
  {
478
478
  "file": "src/room/mirror-sync-service.js",
@@ -501,8 +501,8 @@
501
501
  },
502
502
  {
503
503
  "file": "src/room/session-registry.js",
504
- "beforeBytes": 27201,
505
- "afterBytes": 15360
504
+ "beforeBytes": 27871,
505
+ "afterBytes": 15713
506
506
  },
507
507
  {
508
508
  "file": "src/session/archive-zip.js",
@@ -546,8 +546,8 @@
546
546
  },
547
547
  {
548
548
  "file": "src/session/handlers/chat.js",
549
- "beforeBytes": 35022,
550
- "afterBytes": 14909
549
+ "beforeBytes": 36577,
550
+ "afterBytes": 15412
551
551
  },
552
552
  {
553
553
  "file": "src/session/handlers/control.js",
@@ -601,8 +601,8 @@
601
601
  },
602
602
  {
603
603
  "file": "src/session/manager.js",
604
- "beforeBytes": 28952,
605
- "afterBytes": 14787
604
+ "beforeBytes": 28780,
605
+ "afterBytes": 14785
606
606
  },
607
607
  {
608
608
  "file": "src/session/native-cleanup-outbox.js",
@@ -621,8 +621,8 @@
621
621
  },
622
622
  {
623
623
  "file": "src/session/queue.js",
624
- "beforeBytes": 19312,
625
- "afterBytes": 8831
624
+ "beforeBytes": 24780,
625
+ "afterBytes": 11354
626
626
  },
627
627
  {
628
628
  "file": "src/session/store.js",
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{Command as te}from"commander";import w from"chalk";import re from"node:os";import b from"node:fs";import{loadConfig as d,saveConfig as ie,getConfigPath as se,getShennianDir as ae,resolveShennianPath as J,normalizeAutoUpgradePolicy as ce}from"./config/index.js";import{CliRelayClient as me}from"./relay/client.js";import{registerPairCommand as le,runSmartStart as de}from"./commands/pair.js";import{clearDaemonPidIfOwner as z,createDaemonInstanceId as pe,clearDaemonLauncher as j,findRunningDaemonProcessIds as ge,isRemoteAccessDisabled as ue,isIsolatedDaemonE2eInstance as he,registerDaemonCommand as fe,writeDaemonPid as K,writeDaemonLauncher as W}from"./commands/daemon.js";import{registerRuntimeCommand as Se}from"./commands/runtime.js";import{registerManagerCommand as ve}from"./commands/manager.js";import{registerUpgradeCommand as we}from"./commands/upgrade.js";import{registerToolsCommand as Ie}from"./commands/tools.js";import{registerIntegrationCommand as ye}from"./commands/integration.js";import{registerRoomCommand as Re}from"./commands/room.js";import{SessionManager as Ce}from"./session/manager.js";import{SERVERS as P,regionToUrl as ke,urlToRegion as De}from"./region.js";import{getCurrentVersion as Y,handleStartupCrashCheck as Ee}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Te}from"./upgrade/scheduler.js";import{detectAgents as Ae}from"./agents/detect.js";import{augmentProcessPath as q}from"./env-path.js";q();const Q=Y();import{getCachedAgentInfos as Oe,resolveAgentInfos as Ue}from"./agents/model-registry.js";import{initCliLogReporter as $e,reportLog as I}from"./log-reporter.js";import{isNativeFusionEnabled as Ne}from"./native-fusion/config.js";import{NativeSessionFusionService as be}from"./native-fusion/service.js";import{startDaemonLogRetention as Pe}from"./daemon-log.js";import{startDaemonIpcServer as _e}from"./daemon-ipc/server.js";import{RoomSessionRegistry as Me}from"./room/session-registry.js";import{HttpRoomBindingClient as Le}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as xe}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Ve}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Be}from"./room/mirror-sync-service.js";import{RoomActivationService as He}from"./room/activation-service.js";import{RoomActivationAudit as Ge}from"./room/activation-audit.js";import{RoomAttachmentCache as Fe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Je}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as ze}from"./room/managed-context.js";import{createManagedRoomIpcHandlers as je}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as Ke,SESSION_SYNC_ADVERTISED_CAPABILITIES as We}from"@shennian/wire";import{getDaemonInstallationId as Ye}from"./session/store.js";import{synchronizeMachinePersonalAuthority as qe}from"./personal-sync/machine-authority.js";import{getPersonalSyncLocalMetrics as Qe}from"./personal-sync/metrics.js";import{CLI_SESSION_SYNC_DATA_EPOCH as Xe,ensurePersonalDataEpoch as Ze}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as eo,DeletionSyncTerminalError as oo}from"./session/deletion-sync-state.js";const X=ae(),no=J("daemon.pid");function to(){try{const o=b.readFileSync(no,"utf-8").trim();if(o.startsWith("{")){const i=JSON.parse(o),c=Number(i.pid);return Number.isInteger(c)&&c>0?c:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function ro(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function _(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
- `),process.exitCode=1}function io(o){const n=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&n.push(`reason=${o.reason}`),o.error&&n.push(`error=${o.error}`),n.join(" ")}async function Z(o,n=5e3){const i=Date.now();for(;Date.now()-i<n;){try{process.kill(o,0)}catch{return!0}await new Promise(c=>setTimeout(c,100))}try{return process.kill(o,0),!1}catch{return!0}}const t=new te;t.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(Q),t.enablePositionalOptions(),ye(t),Re(t),t.option("--api <url>","Server URL override").option("--name <name>","Machine name",re.hostname()).action(async o=>{const n=d(),i=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await de(i,o.name)}),t.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const n=J("env.json");try{const e=JSON.parse(b.readFileSync(n,"utf-8"));for(const[s,r]of Object.entries(e))process.env[s]||(process.env[s]=r);q()}catch{}const i=Pe();ue()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(i),process.exit(0));const c=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),M=async(e,s)=>{console.log(`[${new Date().toISOString()}] ${s} (PID ${e})`),process.kill(e,"SIGTERM"),await Z(e)||(process.kill(e,"SIGKILL"),await Z(e,2e3))};try{const e=to();if(e&&e!==process.pid)try{process.kill(e,0),c?await M(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const O=he(process.env,X)?[]:ge(process.pid);if(O.length>0)if(c)for(const e of O)try{await M(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${O[0]}), skipping duplicate start`),process.exit(0);const l=pe();K(process.pid,l,{version:Q}),W(process.pid,void 0,l),process.on("exit",()=>{z(process.pid,l),j(l)}),await Ee()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));try{Ze()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const a=d(),g=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??a.serverUrl??P.cn.url,p=Y(),U=Ae(),h=U.map(e=>e.type),$=Oe(U),y=new Me,R=new Ve,L=new ze(y),f=new Le(g),oe=new Fe({actionClient:f}),ne=new Je({actionClient:f});let x=null;const C=await _e({handlers:{"personal.sync.metrics":()=>Qe(),...je({authority:L,actionClient:f,attachmentCache:oe,attachmentUploader:ne,observations:R,getMachineCredentials:()=>({machineToken:d().machineToken})})}});let V=!1;const k=()=>{V||(V=!0,C.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",k),process.once("SIGTERM",k),!a.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await C.close(),process.exitCode=1;return}process.removeListener("SIGINT",k),process.removeListener("SIGTERM",k);const B=`${ro(g)}/relay/machine`,N=a.machineToken;if(!N)return;try{await qe({serverUrl:g,machineToken:N,machineId:a.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await C.close(),process.exitCode=1;return}a.machineId&&$e(g,a.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${B}... (v${p}) agents: ${h.join(",")}`),I({level:"info",wsEvent:"daemon.start",metadata:{version:p,agents:h}});let D=null,E=null,T=0;const H=ce(a.autoUpgrade),m=new me({serverUrl:B,machineToken:N,cliVersion:p,agentList:h,autoUpgradePolicy:H,sessionSyncProtocolVersion:Ke,sessionSyncCapabilities:We,daemonInstallationId:Ye(),dataEpoch:Xe,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),I({level:"info",wsEvent:"daemon.connected"}),$.some(r=>r.models.length>0)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:h,agents:$}}),Ue(U,{serverUrl:a.serverUrl??g,authToken:a.machineToken??a.accessToken}).then(r=>{JSON.stringify(r)!==JSON.stringify($)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:h,agents:r}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:r,clearUpgradeAttempt:S})=>{const v=r();S(),v&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${v.from} \u2192 ${v.to}`),m.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:v.from,to:v.to}}))}).catch(()=>{}),A.start(),E?.start();const e=++T,s=async()=>{try{await u.synchronizeSessionDeletions(),e===T&&m.getState()==="connected"&&D?.handleConnected()}catch(r){if(console.error("[session.deletions] startup catch-up failed",r),e===T&&m.getState()==="connected"){if(r instanceof oo)return;const S=r instanceof eo?Math.max(1e3,r.retryAfterMs):5e3;setTimeout(()=>{s()},S).unref?.()}}};s()},onDisconnected:e=>{T+=1,E?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${io(e)}`),I({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),I({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),u.handleReq(e)}});D=Ne()?new be(m):null,E=new Be({registry:y,observations:R,actionClient:f,getMachineCredentials:()=>({machineToken:d().machineToken}),onMessages:async(e,s,r,S)=>{await x?.processMessages(e,s,r,S)}});const A=new Te({currentVersion:p,machineId:a.machineId??"unknown-machine",serverUrl:a.serverUrl??g,getIdleState:()=>u.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:s}=await import("./commands/upgrade.js");await s(m,"auto-upgrade",e,{currentVersion:p})}});A.setPolicy(H);const u=new Ce(m,D,p,A,new xe({registry:y,observations:R,bindingClient:f,getMachineCredentials:()=>{const e=d();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${l}`}));x=new He({registry:y,observations:R,activate:e=>u.activateRoomSession(e),issueManagedRoomContext:e=>{const s=d().machineToken;if(!s)throw new Error("machine_not_authorized");return L.issue({registryKey:e,machineCredential:s})},abort:e=>u.abortRoomSession(e),audit:new Ge}),b.mkdirSync(X,{recursive:!0}),K(process.pid,l,{version:p}),W(process.pid,void 0,l),m.connect(),process.stdin.resume();let G=!1;const F=async()=>{if(!G){G=!0,console.log(w.gray(`
3
- Disconnecting...`)),I({level:"info",wsEvent:"daemon.stop"}),clearInterval(i);try{await u.cleanup(),A.stop(),D?.stop(),E?.stop(),m.disconnect(),await C.close(),z(process.pid,l),j(l)}finally{process.exit(0)}}};process.on("SIGINT",()=>{F()}),process.on("SIGTERM",()=>{F()})}),t.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>_()),t.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>_()),t.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>_());const ee=t.command("config").description("View or modify configuration");ee.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${se()}`);const n={...d()};if(n.machineToken&&(n.machineToken=n.machineToken.slice(0,12)+"..."),n.serverUrl){const i=De(n.serverUrl);console.log(`Region: ${i} (${P[i].label})`)}console.log(JSON.stringify(n,null,2))}),ee.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,n)=>{const i=d();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(w.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=n;i.serverUrl=ke(c),ie(i),console.log(w.green(`\u2713 Server set to ${P[c].label}`)),console.log(w.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(w.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),le(t),fe(t),Se(t),ve(t),we(t),Ie(t),t.parse();
1
+ import{Command as te}from"commander";import I from"chalk";import re from"node:os";import P from"node:fs";import{loadConfig as p,saveConfig as ie,getConfigPath as se,getShennianDir as ae,resolveShennianPath as J,normalizeAutoUpgradePolicy as ce}from"./config/index.js";import{CliRelayClient as me}from"./relay/client.js";import{registerPairCommand as le,runSmartStart as de}from"./commands/pair.js";import{clearDaemonPidIfOwner as z,createDaemonInstanceId as pe,clearDaemonLauncher as j,findRunningDaemonProcessIds as ge,isRemoteAccessDisabled as ue,isIsolatedDaemonE2eInstance as he,registerDaemonCommand as fe,writeDaemonPid as K,writeDaemonLauncher as W}from"./commands/daemon.js";import{registerRuntimeCommand as Se}from"./commands/runtime.js";import{registerManagerCommand as ve}from"./commands/manager.js";import{registerUpgradeCommand as Ie}from"./commands/upgrade.js";import{registerToolsCommand as ye}from"./commands/tools.js";import{registerIntegrationCommand as we}from"./commands/integration.js";import{registerRoomCommand as Ce}from"./commands/room.js";import{SessionManager as Re}from"./session/manager.js";import{SERVERS as _,regionToUrl as ke,urlToRegion as De}from"./region.js";import{getCurrentVersion as Y,handleStartupCrashCheck as Ee}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Te}from"./upgrade/scheduler.js";import{detectAgents as Ae}from"./agents/detect.js";import{augmentProcessPath as q}from"./env-path.js";q();const Q=Y();import{getCachedAgentInfos as be,resolveAgentInfos as Oe}from"./agents/model-registry.js";import{initCliLogReporter as Ue,reportLog as y}from"./log-reporter.js";import{isNativeFusionEnabled as Ne}from"./native-fusion/config.js";import{NativeSessionFusionService as $e}from"./native-fusion/service.js";import{startDaemonLogRetention as Pe}from"./daemon-log.js";import{startDaemonIpcServer as _e}from"./daemon-ipc/server.js";import{RoomSessionRegistry as Me}from"./room/session-registry.js";import{HttpRoomBindingClient as xe}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Le}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Ve}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Be}from"./room/mirror-sync-service.js";import{RoomActivationService as He}from"./room/activation-service.js";import{RoomActivationAudit as Ge}from"./room/activation-audit.js";import{RoomAttachmentCache as Fe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Je}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as ze}from"./room/managed-context.js";import{createManagedRoomIpcHandlers as je}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as Ke,SESSION_SYNC_ADVERTISED_CAPABILITIES as We}from"@shennian/wire";import{getDaemonInstallationId as Ye}from"./session/store.js";import{synchronizeMachinePersonalAuthority as qe}from"./personal-sync/machine-authority.js";import{getPersonalSyncLocalMetrics as Qe}from"./personal-sync/metrics.js";import{CLI_SESSION_SYNC_DATA_EPOCH as Xe,ensurePersonalDataEpoch as Ze}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as eo,DeletionSyncTerminalError as oo}from"./session/deletion-sync-state.js";const X=ae(),no=J("daemon.pid");function to(){try{const o=P.readFileSync(no,"utf-8").trim();if(o.startsWith("{")){const a=JSON.parse(o),m=Number(a.pid);return Number.isInteger(m)&&m>0?m:null}const n=parseInt(o,10);return Number.isInteger(n)&&n>0?n:null}catch{return null}}function ro(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function M(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
+ `),process.exitCode=1}function io(o){const n=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&n.push(`reason=${o.reason}`),o.error&&n.push(`error=${o.error}`),n.join(" ")}async function Z(o,n=5e3){const a=Date.now();for(;Date.now()-a<n;){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 r=new te;r.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(Q),r.enablePositionalOptions(),we(r),Ce(r),r.option("--api <url>","Server URL override").option("--name <name>","Machine name",re.hostname()).action(async o=>{const n=p(),a=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??n.serverUrl??void 0;await de(a,o.name)}),r.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async o=>{const n=J("env.json");try{const e=JSON.parse(P.readFileSync(n,"utf-8"));for(const[i,t]of Object.entries(e))process.env[i]||(process.env[i]=t);q()}catch{}const a=Pe();ue()&&(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),x=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await Z(e)||(process.kill(e,"SIGKILL"),await Z(e,2e3))};try{const e=to();if(e&&e!==process.pid)try{process.kill(e,0),m?await x(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 b=he(process.env,X)?[]:ge(process.pid);if(b.length>0)if(m)for(const e of b)try{await x(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${b[0]}), skipping duplicate start`),process.exit(0);const d=pe();K(process.pid,d,{version:Q}),W(process.pid,void 0,d),process.on("exit",()=>{z(process.pid,d),j(d)}),await Ee()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));try{Ze()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const c=p(),u=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??c.serverUrl??_.cn.url,g=Y(),O=Ae(),S=O.map(e=>e.type),U=be(O),w=new Me,C=new Ve,L=new ze(w),h=new xe(u),oe=new Fe({actionClient:h}),ne=new Je({actionClient:h});let N=null;const R=await _e({handlers:{"personal.sync.metrics":()=>Qe(),...je({authority:L,actionClient:h,attachmentCache:oe,attachmentUploader:ne,observations:C,getMachineCredentials:()=>({machineToken:p().machineToken})})}});let V=!1;const k=()=>{V||(V=!0,R.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",k),process.once("SIGTERM",k),!c.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await R.close(),process.exitCode=1;return}process.removeListener("SIGINT",k),process.removeListener("SIGTERM",k);const B=`${ro(u)}/relay/machine`,$=c.machineToken;if(!$)return;try{await qe({serverUrl:u,machineToken:$,machineId:c.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await R.close(),process.exitCode=1;return}c.machineId&&Ue(u,c.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${B}... (v${g}) agents: ${S.join(",")}`),y({level:"info",wsEvent:"daemon.start",metadata:{version:g,agents:S}});let D=null,E=null,T=0;const H=ce(c.autoUpgrade),l=new me({serverUrl:B,machineToken:$,cliVersion:g,agentList:S,autoUpgradePolicy:H,sessionSyncProtocolVersion:Ke,sessionSyncCapabilities:We,daemonInstallationId:Ye(),dataEpoch:Xe,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),y({level:"info",wsEvent:"daemon.connected"}),U.some(t=>t.models.length>0)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:S,agents:U}}),Oe(O,{serverUrl:c.serverUrl??u,authToken:c.machineToken??c.accessToken}).then(t=>{JSON.stringify(t)!==JSON.stringify(U)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:S,agents:t}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:t,clearUpgradeAttempt:s})=>{const v=t();s(),v&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${v.from} \u2192 ${v.to}`),l.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:v.from,to:v.to}}))}).catch(()=>{}),A.start(),E?.start();const e=++T,i=async()=>{try{await f.synchronizeSessionDeletions(),e===T&&l.getState()==="connected"&&D?.handleConnected()}catch(t){if(console.error("[session.deletions] startup catch-up failed",t),e===T&&l.getState()==="connected"){if(t instanceof oo)return;const s=t instanceof eo?Math.max(1e3,t.retryAfterMs):5e3;setTimeout(()=>{i()},s).unref?.()}}};i()},onDisconnected:e=>{T+=1,E?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${io(e)}`),y({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}`),y({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),f.handleReq(e)}});D=Ne()?new $e(l):null,E=new Be({registry:w,observations:C,actionClient:h,getMachineCredentials:()=>({machineToken:p().machineToken}),onMessages:async(e,i,t,s)=>{await N?.processMessages(e,i,t,s)}});const A=new Te({currentVersion:g,machineId:c.machineId??"unknown-machine",serverUrl:c.serverUrl??u,getIdleState:()=>f.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(l,"auto-upgrade",e,{currentVersion:g})}});A.setPolicy(H);const f=new Re(l,D,g,A,new Le({registry:w,observations:C,bindingClient:h,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${d}`}));N=new He({registry:w,observations:C,enqueueConversationBatch:e=>f.enqueueManagedRoomBatch(e),loadContext:async e=>{const i=p().machineToken?.trim(),t=e.binding;if(!i||!t)throw new Error("managed_room_context_unavailable");const s=await h.status({machineToken:i,roomId:t.roomId,bindingId:t.bindingId});return{roomName:s.room.name,memberCount:s.room.memberCount,members:s.room.members,agentsByIdentityId:s.agent?{[s.agentIdentityId]:s.agent}:{},agentSystemPromptsByIdentityId:s.agent?{[s.agentIdentityId]:s.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,i)=>{const t=p().machineToken;if(!t)throw new Error("machine_not_authorized");return L.issue({registryKey:e,machineCredential:t,activationCauseMessageId:i})},audit:new Ge}),f.setManagedRoomTurnPreparer(e=>N.prepareConversationTurn(e)),P.mkdirSync(X,{recursive:!0}),K(process.pid,d,{version:g}),W(process.pid,void 0,d),l.connect(),process.stdin.resume();let G=!1;const F=async()=>{if(!G){G=!0,console.log(I.gray(`
3
+ Disconnecting...`)),y({level:"info",wsEvent:"daemon.stop"}),clearInterval(a);try{await f.cleanup(),A.stop(),D?.stop(),E?.stop(),l.disconnect(),await R.close(),z(process.pid,d),j(d)}finally{process.exit(0)}}};process.on("SIGINT",()=>{F()}),process.on("SIGTERM",()=>{F()})}),r.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>M()),r.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(()=>M()),r.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>M());const ee=r.command("config").description("View or modify configuration");ee.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${se()}`);const n={...p()};if(n.machineToken&&(n.machineToken=n.machineToken.slice(0,12)+"..."),n.serverUrl){const a=De(n.serverUrl);console.log(`Region: ${a} (${_[a].label})`)}console.log(JSON.stringify(n,null,2))}),ee.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,n)=>{const a=p();if(o==="server"){n!=="cn"&&n!=="global"&&(console.error(I.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const m=n;a.serverUrl=ke(m),ie(a),console.log(I.green(`\u2713 Server set to ${_[m].label}`)),console.log(I.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(I.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),le(r),fe(r),Se(r),ve(r),Ie(r),ye(r),r.parse();
@@ -1,17 +1,11 @@
1
- import type { AgentType, RoomMessageView } from '@shennian/wire';
1
+ import type { RoomMessageView } from '@shennian/wire';
2
+ import type { ManagedRoomEnqueueParams, ManagedRoomQueueContext, PreparedManagedRoomTurn } from '../session/queue.js';
2
3
  import type { RoomHostSessionObservationStore } from './host-session-observations.js';
3
4
  import { type RoomSessionRegistration, type RoomSessionRegistry } from './session-registry.js';
4
5
  import type { RoomActivationAudit } from './activation-audit.js';
5
- export type RoomAgentActivation = {
6
- sessionId: string;
7
- agentType: AgentType;
8
- agentSessionId: string;
9
- workDir: string;
10
- text: string;
11
- clientMessageId: string;
12
- systemPrompt: string;
13
- roomContextRegistryKey: string;
14
- managedRoomContextToken: string;
6
+ export type RoomConversationQueueInput = {
7
+ params: ManagedRoomEnqueueParams;
8
+ context: ManagedRoomQueueContext;
15
9
  };
16
10
  export type RoomActivationContext = {
17
11
  roomName: string;
@@ -32,22 +26,17 @@ export type RoomActivationContext = {
32
26
  };
33
27
  export declare class RoomActivationService {
34
28
  private readonly deps;
35
- private readonly promptBodies;
29
+ private readonly latestContexts;
36
30
  constructor(deps: {
37
31
  registry: RoomSessionRegistry;
38
32
  observations: RoomHostSessionObservationStore;
39
- activate: (input: RoomAgentActivation) => Promise<{
40
- externalOwnerQueued?: boolean;
41
- } | void>;
42
- issueManagedRoomContext?: (registryKey: string) => string;
43
- abort: (input: Pick<RoomAgentActivation, 'sessionId' | 'agentType'>) => Promise<void>;
33
+ issueManagedRoomContext: (registryKey: string, activationCauseMessageId?: string) => string;
44
34
  now?: () => Date;
45
- activationTurnTimeoutMs?: number;
46
35
  audit?: Pick<RoomActivationAudit, 'record'>;
36
+ enqueueConversationBatch: (inputs: RoomConversationQueueInput[]) => Promise<void>;
37
+ loadContext?: (registration: RoomSessionRegistration) => Promise<RoomActivationContext>;
47
38
  });
48
39
  processMessages(roomId: string, messages: RoomMessageView[], registrations: RoomSessionRegistration[], context?: RoomActivationContext): Promise<void>;
49
- drain(registryKey: string): Promise<void>;
50
- private prunePromptBodies;
51
- private stopActiveActivation;
40
+ prepareConversationTurn(contexts: ManagedRoomQueueContext[]): Promise<PreparedManagedRoomTurn>;
52
41
  private audit;
53
42
  }
@@ -1,8 +1,8 @@
1
- import{selectRoomActivationTargets as A}from"./activation-policy.js";import{isInterruptedRoomActivation as w}from"./session-registry.js";import{composeManagedRoomPrompt as _}from"./managed-prompt-composer.js";const S=4,$=6e4,B=6,l=5*6e4,C=5*6e4;class E{deps;promptBodies=new Map;constructor(e){this.deps=e}async processMessages(e,t,s,n={roomName:"\u5F53\u524D\u7FA4\u804A"}){const r=[...t].sort((o,d)=>q(o.sequence,d.sequence));for(const o of r){const d=new Set(A(o,s).map(c=>c.registration.registryKey));for(const c of s){const a=this.deps.registry.getByRegistryKey(c.registryKey);if(!(!a?.binding||a.binding.roomId!==e)&&!(a.binding.status!=="active"&&a.binding.status!=="paused")&&!(BigInt(o.sequence)<=BigInt(a.binding.lastActivatedSequence))){if(d.has(a.registryKey)){const p=(o.activationCause?.hopCount??0)+(o.author.principalType==="agent"?1:0),m=o.activationCause?.rootCauseMessageId??o.id;if(p>S)this.audit(a,o.id,m,p,"suppressed","max_hops");else{const g=a.binding.activeActivation;g&&w(g)&&await this.stopActiveActivation(a,"suppressed","daemon_restart_interrupted",this.deps.now?.()??new Date);const h=f([o]),b=a.binding.activationMode==="mention_only"?x([...(a.binding.mentionContext??[]).map(M=>M.deliveryText),h]):h,u=D(n,a);this.promptBodies.set(y(a.registryKey,o.id),u.systemPrompt),this.prunePromptBodies(),this.deps.registry.enqueueActivation(a.registryKey,{causeMessageId:o.id,rootCauseMessageId:m,sequence:o.sequence,hopCount:p,enqueuedAt:(this.deps.now?.()??new Date).toISOString(),attempts:0,deliveryText:b,promptSnapshot:{managedRoomPolicyVersion:u.snapshot.managedRoomPolicyVersion,sessionModeInstructionsHash:u.snapshot.sessionModeInstructionsHash,agentSystemPromptHash:u.snapshot.agentSystemPromptHash,trustedRoomContextHash:u.snapshot.trustedRoomContextHash}},this.deps.now?.()??new Date,a.binding.activationMode==="mention_only"),this.audit(a,o.id,m,p,"queued","policy_match")}}else a.binding.activationMode==="mention_only"&&o.kind!=="system"&&o.author.membershipId!==a.binding.agentMembershipId&&this.deps.registry.appendMentionContext(a.registryKey,{sequence:o.sequence,deliveryText:f([o])},this.deps.now?.()??new Date);this.deps.registry.advanceCursor({registryKey:a.registryKey,cursor:"activated",sequence:o.sequence})}}}for(const o of s)await this.drain(o.registryKey)}async drain(e){await this.deps.registry.runExclusive(e,async()=>{let t=this.deps.registry.getByRegistryKey(e),s=t?.binding;if(!t||!s)return;if(s.activeActivation){const d=this.deps.now?.()??new Date,c=this.deps.activationTurnTimeoutMs??C,a=Date.parse(s.activeActivation.dispatchedAt??s.activeActivation.enqueuedAt);if(d.getTime()-a<c||!await this.stopActiveActivation(t,"failed","turn_timeout",d)||(t=this.deps.registry.getByRegistryKey(e),s=t?.binding,!t||!s))return}const n=s.activationQueue[0];if(!n||s.status!=="active"||s.activationMode==="paused"||!t.capabilities.canResumeIdleSession||t.lifecycleState!=="inactive"&&!t.capabilities.canActivateWhileHostOpen)return;const r=this.deps.observations.getBySource(t);if(!r?.workspacePath||r.lifecycleState==="closed")return;const o=this.deps.registry.claimActivation({registryKey:e,windowMs:$,maxActivations:B,pauseMs:l,now:this.deps.now?.()});if(!o.allowed){o.newlyPaused&&this.audit(t,n.causeMessageId,n.rootCauseMessageId,n.hopCount,"suppressed","rate_limited");return}this.deps.registry.markActivationDispatching(e,n.causeMessageId);try{const d=n.systemPrompt??this.promptBodies.get(y(e,n.causeMessageId));if(!d)throw new Error("managed_room_prompt_snapshot_unavailable");const c=await this.deps.activate({sessionId:t.nianSessionId,agentType:I(t.runtimeAdapter),agentSessionId:t.sourceSessionKey,workDir:r.workspacePath,text:n.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u65B0\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",systemPrompt:d,roomContextRegistryKey:t.registryKey,managedRoomContextToken:this.deps.issueManagedRoomContext?.(t.registryKey)??"",clientMessageId:`room-activation-${s.bindingId}-${n.causeMessageId}`});this.deps.registry.updateLifecycle(e,c?.externalOwnerQueued?"inactive":"active"),this.promptBodies.delete(y(e,n.causeMessageId)),this.audit(t,n.causeMessageId,n.rootCauseMessageId,n.hopCount,"activated","idle_resume")}catch(d){throw this.deps.registry.retryActiveActivation({registryKey:e,maxAttempts:3,pauseMs:l,now:this.deps.now?.()}),this.audit(t,n.causeMessageId,n.rootCauseMessageId,n.hopCount,"failed",d instanceof Error?d.name:"activation_error"),d}})}prunePromptBodies(){for(;this.promptBodies.size>500;){const e=this.promptBodies.keys().next().value;if(!e)return;this.promptBodies.delete(e)}}async stopActiveActivation(e,t,s,n){const r=e.binding?.activeActivation;if(!r)return!0;try{await this.deps.abort({sessionId:e.nianSessionId,agentType:I(e.runtimeAdapter)})}catch(o){return this.audit(e,r.causeMessageId,r.rootCauseMessageId,r.hopCount,"failed",o instanceof Error?`${s}_${o.name}`:`${s}_abort_error`),!1}return this.deps.registry.clearActiveActivation(e.registryKey,n),this.deps.registry.updateLifecycle(e.registryKey,"inactive",n),this.audit(e,r.causeMessageId,r.rootCauseMessageId,r.hopCount,t,s),!0}audit(e,t,s,n,r,o){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:t,rootCauseMessageId:s,hopCount:n,outcome:r,reason:o,at:this.deps.now?.()})}}function f(i){const e=i.map(t=>{const s=t.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",n=t.attachments.length>0?`
2
- ${t.attachments.map(T).join(`
3
- `)}`:"";return{speaker:t.author.displayName,body:`${s}${n}`}});return v(e)}function T(i){return`\u9644\u4EF6\uFF1A${`${P(i.name)} \xB7 ${R(i.byteSize)}`}\uFF5C${i.mimeType}\uFF5CattachmentId=${i.attachmentId}`}function P(i){return String(i||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function R(i){const e=Number(i);if(!Number.isFinite(e)||e<0)return`${i} B`;if(e<1024)return`${e} B`;const t=["KB","MB","GB","TB"];let s=e,n=-1;do s/=1024,n+=1;while(s>=1024&&n<t.length-1);const r=s>=100?0:s>=10?1:2;return`${s.toFixed(r).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${t[n]}`}function x(i){return v(i.map(k))}function k(i){const e=i.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const t=i.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const s=i.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(s)return{speaker:s[1],body:s[2]};const n=i.match(/^([^\n]+)\n([\s\S]*)$/);return n?{speaker:n[1],body:n[2]}:{speaker:"\u7FA4\u6210\u5458",body:i.trim()}}function v(i){if(i.length===1){const[e]=i;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
4
- ${e.body}`}return`[\u7FA4\u6D88\u606F]
5
- ${i.map(e=>`<${e.speaker}>
6
- ${e.body}`).join(`
1
+ import{selectRoomActivationTargets as C}from"./activation-policy.js";import{composeManagedRoomPrompt as $}from"./managed-prompt-composer.js";const S=4,x=6e4,q=6,A=5*6e4;class z{deps;latestContexts=new Map;constructor(t){this.deps=t}async processMessages(t,i,o,a={roomName:"\u5F53\u524D\u7FA4\u804A"}){const c=[],d=[],y=new Map,h=new Set,w=[...i].sort((e,r)=>N(e.sequence,r.sequence));for(const e of o){const r=this.deps.registry.getByRegistryKey(e.registryKey);if(!r?.binding||r.binding.roomId!==t||r.binding.activationQueue.length===0&&!r.binding.activeActivation||(h.add(r.registryKey),r.binding.activationQueue.length===0))continue;const u=this.deps.observations.getBySource(r);if(!u?.workspacePath||u.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(r.registryKey,a);for(const n of r.binding.activationQueue)c.push({registration:r,input:{params:{sessionId:r.nianSessionId,text:n.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:_(r.runtimeAdapter),workDir:u.workspacePath,agentSessionId:r.sourceSessionKey,queueMessageId:`room-activation-${r.binding.bindingId}-${n.causeMessageId}`,clientMessageId:`room-activation-${r.binding.bindingId}-${n.causeMessageId}`},context:{registryKey:r.registryKey,causeMessageId:n.causeMessageId,rootCauseMessageId:n.rootCauseMessageId,sequence:n.sequence,hopCount:n.hopCount}}})}for(const e of w){const r=new Set(C(e,o).map(u=>u.registration.registryKey));for(const u of o){const n=this.deps.registry.getByRegistryKey(u.registryKey);if(!n?.binding||n.binding.roomId!==t||n.binding.status!=="active"&&n.binding.status!=="paused"||BigInt(e.sequence)<=BigInt(n.binding.lastActivatedSequence))continue;const p=y.get(n.registryKey)??[...n.binding.mentionContext??[]];if(y.set(n.registryKey,p),r.has(n.registryKey)){const g=(e.activationCause?.hopCount??0)+(e.author.principalType==="agent"?1:0),m=e.activationCause?.rootCauseMessageId??e.id;if(g>S)this.audit(n,e.id,m,g,"suppressed","max_hops");else{const b=this.deps.registry.claimActivation({registryKey:n.registryKey,windowMs:x,maxActivations:q,pauseMs:A,now:this.deps.now?.()});if(!b.allowed){b.newlyPaused&&this.audit(n,e.id,m,g,"suppressed","rate_limited"),d.push({registryKey:n.registryKey,sequence:e.sequence});continue}const l=this.deps.observations.getBySource(n);if(!l?.workspacePath||l.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const f=I([e]),M=n.binding.activationMode==="mention_only"?P([...p.map(K=>K.deliveryText),f]):f;this.latestContexts.set(n.registryKey,a),c.push({registration:n,input:{params:{sessionId:n.nianSessionId,text:M,agentType:_(n.runtimeAdapter),workDir:l.workspacePath,agentSessionId:n.sourceSessionKey,queueMessageId:`room-activation-${n.binding.bindingId}-${e.id}`,clientMessageId:`room-activation-${n.binding.bindingId}-${e.id}`},context:{registryKey:n.registryKey,causeMessageId:e.id,rootCauseMessageId:m,sequence:e.sequence,hopCount:g}}}),n.binding.activationMode==="mention_only"&&y.set(n.registryKey,[])}}else n.binding.activationMode==="mention_only"&&e.kind!=="system"&&e.author.membershipId!==n.binding.agentMembershipId&&(p.some(g=>g.sequence===e.sequence)||p.push({sequence:e.sequence,deliveryText:I([e])}));d.push({registryKey:n.registryKey,sequence:e.sequence})}}await this.deps.enqueueConversationBatch(c.map(e=>e.input));for(const e of h)this.deps.registry.retireLegacyActivationState(e,this.deps.now?.()??new Date);for(const[e,r]of y)this.deps.registry.replaceMentionContext(e,r,this.deps.now?.()??new Date);for(const e of c)this.audit(e.registration,e.input.context.causeMessageId,e.input.context.rootCauseMessageId,e.input.context.hopCount,"queued","conversation_queue");for(const e of d)this.deps.registry.advanceCursor({registryKey:e.registryKey,cursor:"activated",sequence:e.sequence})}async prepareConversationTurn(t){const i=t.at(-1);if(!i)throw new Error("managed_room_queue_context_missing");const o=this.deps.registry.getByRegistryKey(i.registryKey);if(!o?.binding||o.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const a=this.deps.loadContext?await this.deps.loadContext(o):this.latestContexts.get(i.registryKey);if(!a)throw new Error("managed_room_prompt_context_unavailable");const c=D(a,o),d=this.deps.issueManagedRoomContext(o.registryKey,i.causeMessageId);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(d))throw new Error("managed_room_context_unavailable");return this.audit(o,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"activated","conversation_queue_dispatch"),{systemPrompt:c.systemPrompt,roomContextRegistryKey:o.registryKey,managedRoomContextToken:d}}audit(t,i,o,a,c,d){t.binding&&this.deps.audit?.record({roomId:t.binding.roomId,bindingId:t.binding.bindingId,causeMessageId:i,rootCauseMessageId:o,hopCount:a,outcome:c,reason:d,at:this.deps.now?.()})}}function I(s){const t=s.map(i=>{const o=i.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",a=i.attachments.length>0?`
2
+ ${i.attachments.map(k).join(`
3
+ `)}`:"";return{speaker:i.author.displayName,body:`${o}${a}`}});return v(t)}function k(s){return`\u9644\u4EF6\uFF1A${`${B(s.name)} \xB7 ${T(s.byteSize)}`}\uFF5C${s.mimeType}\uFF5CattachmentId=${s.attachmentId}`}function B(s){return String(s||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function T(s){const t=Number(s);if(!Number.isFinite(t)||t<0)return`${s} B`;if(t<1024)return`${t} B`;const i=["KB","MB","GB","TB"];let o=t,a=-1;do o/=1024,a+=1;while(o>=1024&&a<i.length-1);const c=o>=100?0:o>=10?1:2;return`${o.toFixed(c).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${i[a]}`}function P(s){return v(s.map(R))}function R(s){const t=s.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const i=s.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(i)return{speaker:i[1],body:i[2]};const o=s.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(o)return{speaker:o[1],body:o[2]};const a=s.match(/^([^\n]+)\n([\s\S]*)$/);return a?{speaker:a[1],body:a[2]}:{speaker:"\u7FA4\u6210\u5458",body:s.trim()}}function v(s){if(s.length===1){const[t]=s;return`[\u7FA4\u6D88\u606F] <${t.speaker}>
4
+ ${t.body}`}return`[\u7FA4\u6D88\u606F]
5
+ ${s.map(t=>`<${t.speaker}>
6
+ ${t.body}`).join(`
7
7
 
8
- `)}`}function D(i,e){const t=e.binding?i.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!t)throw new Error("managed_room_prompt_incomplete");return _({agent:{displayName:t.displayName,description:t.description,systemPrompt:i.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId]},room:{name:i.roomName,activationMode:e.binding.activationMode,members:i.members}})}function y(i,e){return`${i}:${e}`}function I(i){return i==="claude_code"?"claude":i}function q(i,e){const t=BigInt(i),s=BigInt(e);return t<s?-1:t>s?1:0}export{E as RoomActivationService};
8
+ `)}`}function D(s,t){const i=t.binding?s.agentsByIdentityId?.[t.binding.agentIdentityId]:void 0;if(!t.binding||!i)throw new Error("managed_room_prompt_incomplete");return $({agent:{displayName:i.displayName,description:i.description,systemPrompt:s.agentSystemPromptsByIdentityId?.[t.binding.agentIdentityId]},room:{name:s.roomName,activationMode:t.binding.activationMode,members:s.members}})}function _(s){return s==="claude_code"?"claude":s}function N(s,t){const i=BigInt(s),o=BigInt(t);return i<o?-1:i>o?1:0}export{z as RoomActivationService};
@@ -7,6 +7,7 @@ type ManagedRoomGrant = {
7
7
  registrationEpoch: number;
8
8
  credentialHash: string;
9
9
  allowedActions: ReadonlySet<ManagedRoomAction>;
10
+ activationCauseMessageId?: string;
10
11
  expiresAt: number;
11
12
  };
12
13
  export declare class ManagedRoomContextError extends Error {
@@ -28,6 +29,7 @@ export declare class ManagedRoomContextAuthority {
28
29
  machineCredential: string;
29
30
  ttlMs?: number;
30
31
  allowedActions?: ManagedRoomAction[];
32
+ activationCauseMessageId?: string;
31
33
  }): string;
32
34
  resolve(input: {
33
35
  token: string;
@@ -1 +1 @@
1
- import g from"node:crypto";const h="SHENNIAN_MANAGED_ROOM_CONTEXT";class r extends Error{code;constructor(e,t){super(t),this.code=e,this.name="ManagedRoomContextError"}}class m{registry;now;grants=new Map;constructor(e,t=Date.now){this.registry=e,this.now=t}issue(e){const t=this.registry.getByRegistryKey(e.registryKey);if(!t?.binding||t.lifecycleState==="closed")throw new r("managed_room_context_unavailable","Managed Room context is unavailable.");if(t.binding.status!=="active"&&t.binding.status!=="paused")throw new r("managed_room_binding_unavailable","Managed Room binding is unavailable.");if(!t.binding.agentMembershipId)throw new r("managed_room_membership_unavailable","Managed Room membership is unavailable.");const n=e.machineCredential.trim();if(!n)throw new r("machine_not_authorized","This machine is not authorized yet.");const o=`mrc_${g.randomBytes(32).toString("base64url")}`,a=s(o);return this.grants.set(a,{tokenHash:a,registryKey:t.registryKey,registrationEpoch:t.registrationEpoch,credentialHash:s(n),allowedActions:new Set(e.allowedActions??["status","read","send","attachment.get"]),expiresAt:this.now()+Math.min(Math.max(e.ttlMs??10*6e4,1e3),30*6e4)}),this.prune(),o}resolve(e){const t=s(e.token),n=this.grants.get(t);if(!n||n.expiresAt<=this.now())throw this.grants.delete(t),new r("managed_room_context_expired","Managed Room context expired.");if(!d(n.credentialHash,s(e.machineCredential)))throw new r("managed_room_context_invalid","Managed Room context is invalid.");if(!n.allowedActions.has(e.action))throw new r("managed_room_action_forbidden","This action is not allowed in the managed Room context.");const o=this.registry.getByRegistryKey(n.registryKey),a=o?.binding;if(!o||!a||o.registrationEpoch!==n.registrationEpoch||o.lifecycleState==="closed"||a.status!=="active"&&a.status!=="paused"||!a.agentMembershipId)throw this.grants.delete(t),new r("managed_room_context_revoked","Managed Room context was revoked.");return{grant:n,registration:o,binding:a}}revokeToken(e){this.grants.delete(s(e))}revokeSession(e){for(const[t,n]of this.grants)n.registryKey===e&&this.grants.delete(t)}prune(){const e=this.now();for(const[t,n]of this.grants)n.expiresAt<=e&&this.grants.delete(t)}}function s(i){return g.createHash("sha256").update(i,"utf8").digest("hex")}function d(i,e){return g.timingSafeEqual(Buffer.from(i,"hex"),Buffer.from(e,"hex"))}export{h as MANAGED_ROOM_CONTEXT_ENV,m as ManagedRoomContextAuthority,r as ManagedRoomContextError};
1
+ import g from"node:crypto";const h="SHENNIAN_MANAGED_ROOM_CONTEXT";class i extends Error{code;constructor(e,t){super(t),this.code=e,this.name="ManagedRoomContextError"}}class m{registry;now;grants=new Map;constructor(e,t=Date.now){this.registry=e,this.now=t}issue(e){const t=this.registry.getByRegistryKey(e.registryKey);if(!t?.binding||t.lifecycleState==="closed")throw new i("managed_room_context_unavailable","Managed Room context is unavailable.");if(t.binding.status!=="active"&&t.binding.status!=="paused")throw new i("managed_room_binding_unavailable","Managed Room binding is unavailable.");if(!t.binding.agentMembershipId)throw new i("managed_room_membership_unavailable","Managed Room membership is unavailable.");const n=e.machineCredential.trim();if(!n)throw new i("machine_not_authorized","This machine is not authorized yet.");const a=`mrc_${g.randomBytes(32).toString("base64url")}`,o=s(a);return this.grants.set(o,{tokenHash:o,registryKey:t.registryKey,registrationEpoch:t.registrationEpoch,credentialHash:s(n),allowedActions:new Set(e.allowedActions??["status","read","send","attachment.get"]),activationCauseMessageId:e.activationCauseMessageId,expiresAt:this.now()+Math.min(Math.max(e.ttlMs??10*6e4,1e3),30*6e4)}),this.prune(),a}resolve(e){const t=s(e.token),n=this.grants.get(t);if(!n||n.expiresAt<=this.now())throw this.grants.delete(t),new i("managed_room_context_expired","Managed Room context expired.");if(!d(n.credentialHash,s(e.machineCredential)))throw new i("managed_room_context_invalid","Managed Room context is invalid.");if(!n.allowedActions.has(e.action))throw new i("managed_room_action_forbidden","This action is not allowed in the managed Room context.");const a=this.registry.getByRegistryKey(n.registryKey),o=a?.binding;if(!a||!o||a.registrationEpoch!==n.registrationEpoch||a.lifecycleState==="closed"||o.status!=="active"&&o.status!=="paused"||!o.agentMembershipId)throw this.grants.delete(t),new i("managed_room_context_revoked","Managed Room context was revoked.");return{grant:n,registration:a,binding:o}}revokeToken(e){this.grants.delete(s(e))}revokeSession(e){for(const[t,n]of this.grants)n.registryKey===e&&this.grants.delete(t)}prune(){const e=this.now();for(const[t,n]of this.grants)n.expiresAt<=e&&this.grants.delete(t)}}function s(r){return g.createHash("sha256").update(r,"utf8").digest("hex")}function d(r,e){return g.timingSafeEqual(Buffer.from(r,"hex"),Buffer.from(e,"hex"))}export{h as MANAGED_ROOM_CONTEXT_ENV,m as ManagedRoomContextAuthority,i as ManagedRoomContextError};
@@ -1 +1 @@
1
- import w from"node:crypto";import{DaemonIpcError as o}from"../daemon-ipc/protocol.js";import{ManagedRoomContextError as x}from"./managed-context.js";function T(e){return{"managed-room.execute":async t=>{try{const n=k(t),i=c(n.context,"context",128),m=A(n.action),f=e.getMachineCredentials().machineToken?.trim();if(!f)throw new o("machine_not_authorized","This machine is not authorized yet.");const{registration:I,binding:r}=e.authority.resolve({token:i,action:m,machineCredential:f}),s={machineToken:f,roomId:r.roomId,bindingId:r.bindingId};if(m==="status"){const a=await e.actionClient.status(s);return u({room:{name:a.room.name,status:a.room.status,memberCount:a.room.memberCount??a.room.members?.length??0,members:a.room.members??[]},agent:a.agent??null,activationMode:a.activationMode})}if(m==="read"){const a=_(n.before,"before"),p=_(n.after,"after");if(a&&p)throw new o("ambiguous_sequence_cursor","before and after are mutually exclusive.");const M=S(n.limit),v=await e.actionClient.read({...s,beforeSequence:a,afterSequence:p,limit:M});return u({messages:v.messages.map(y)})}if(m==="attachment.get"){const a=c(n.attachmentId,"attachmentId",191);return u(await e.attachmentCache.materialize({...s,attachmentId:a,contextKey:i}))}const l=typeof n.text=="string"?n.text.trim():"",d=Array.isArray(n.files)?n.files.map(a=>c(a,"file",4096)):[];if(!l&&d.length===0)throw new o("room_message_empty","Provide text or at least one file.");if(d.length>10)throw new o("room_attachments_too_many","A message can contain at most 10 files.");let h;if(d.length){const a=e.observations.getBySource(I);if(!a?.workspacePath)throw new o("managed_room_workspace_unavailable","Managed Agent workspace is unavailable.");h=await e.attachmentUploader.upload({...s,workspacePath:a.workspacePath,clientMessageId:b(n.clientMessageId)??`managed:${w.randomUUID()}`,files:d})}const g=await e.actionClient.send({...s,clientMessageId:b(n.clientMessageId)??`managed:${w.randomUUID()}`,text:l,attachmentIds:h,activationCauseMessageId:r.activeActivation&&!("interruptedAt"in r.activeActivation)?r.activeActivation.causeMessageId:void 0});return u({message:y(g.message),idempotent:g.idempotent})}catch(n){if(n instanceof o)throw n;if(n instanceof x)throw new o(n.code,n.message);const i=n;throw new o(typeof i?.code=="string"?i.code:"managed_room_action_failed",typeof i?.message=="string"?i.message:"Managed Room action failed.")}}}}function y(e){return{sequence:e.sequence,kind:e.kind,source:e.source??"app",author:{nickname:e.author.displayName,type:e.author.principalType,...e.author.ownerDisplayName?{ownerNickname:e.author.ownerDisplayName}:{}},mentions:(e.mentions??[]).map(t=>({nickname:t.displayName,type:t.principalType})),text:e.text??null,attachments:e.attachments.map(t=>({attachmentId:t.attachmentId,mediaKind:t.mediaKind,name:t.name,mimeType:t.mimeType,byteSize:t.byteSize,sha256:t.sha256})),createdAt:e.createdAt}}function k(e){if(!e||Array.isArray(e)||typeof e!="object")throw new o("invalid_params","Structured parameters are required.");return e}function c(e,t,n){if(typeof e!="string"||!e.trim()||e.length>n)throw new o("invalid_params",`${t} is invalid.`);return e.trim()}function b(e){return typeof e=="string"&&e.trim()?e.trim():void 0}function A(e){const t=c(e,"action",32);if(t!=="status"&&t!=="read"&&t!=="send"&&t!=="attachment.get")throw new o("managed_room_action_forbidden","This action is not available in managed Room context.");return t}function _(e,t){if(e==null)return;const n=c(e,t,20);if(!/^(0|[1-9]\d{0,19})$/.test(n))throw new o("invalid_sequence",`${t} is invalid.`);return n}function S(e){const t=e==null?50:Number(e);if(!Number.isInteger(t)||t<1||t>50)throw new o("invalid_limit","limit must be between 1 and 50.");return t}function u(e){return JSON.parse(JSON.stringify(e))}export{T as createManagedRoomIpcHandlers};
1
+ import w from"node:crypto";import{DaemonIpcError as o}from"../daemon-ipc/protocol.js";import{ManagedRoomContextError as C}from"./managed-context.js";function z(e){return{"managed-room.execute":async t=>{try{const n=S(t),r=s(n.context,"context",128),c=q(n.action),u=e.getMachineCredentials().machineToken?.trim();if(!u)throw new o("machine_not_authorized","This machine is not authorized yet.");const{grant:I,registration:M,binding:f}=e.authority.resolve({token:r,action:c,machineCredential:u}),i={machineToken:u,roomId:f.roomId,bindingId:f.bindingId};if(c==="status"){const a=await e.actionClient.status(i);return d({room:{name:a.room.name,status:a.room.status,memberCount:a.room.memberCount??a.room.members?.length??0,members:a.room.members??[]},agent:a.agent??null,activationMode:a.activationMode})}if(c==="read"){const a=_(n.before,"before"),p=_(n.after,"after");if(a&&p)throw new o("ambiguous_sequence_cursor","before and after are mutually exclusive.");const x=v(n.limit),k=await e.actionClient.read({...i,beforeSequence:a,afterSequence:p,limit:x});return d({messages:k.messages.map(y)})}if(c==="attachment.get"){const a=s(n.attachmentId,"attachmentId",191);return d(await e.attachmentCache.materialize({...i,attachmentId:a,contextKey:r}))}const l=typeof n.text=="string"?n.text.trim():"",m=Array.isArray(n.files)?n.files.map(a=>s(a,"file",4096)):[];if(!l&&m.length===0)throw new o("room_message_empty","Provide text or at least one file.");if(m.length>10)throw new o("room_attachments_too_many","A message can contain at most 10 files.");let h;if(m.length){const a=e.observations.getBySource(M);if(!a?.workspacePath)throw new o("managed_room_workspace_unavailable","Managed Agent workspace is unavailable.");h=await e.attachmentUploader.upload({...i,workspacePath:a.workspacePath,clientMessageId:b(n.clientMessageId)??`managed:${w.randomUUID()}`,files:m})}const g=await e.actionClient.send({...i,clientMessageId:b(n.clientMessageId)??`managed:${w.randomUUID()}`,text:l,attachmentIds:h,activationCauseMessageId:I.activationCauseMessageId});return d({message:y(g.message),idempotent:g.idempotent})}catch(n){if(n instanceof o)throw n;if(n instanceof C)throw new o(n.code,n.message);const r=n;throw new o(typeof r?.code=="string"?r.code:"managed_room_action_failed",typeof r?.message=="string"?r.message:"Managed Room action failed.")}}}}function y(e){return{sequence:e.sequence,kind:e.kind,source:e.source??"app",author:{nickname:e.author.displayName,type:e.author.principalType,...e.author.ownerDisplayName?{ownerNickname:e.author.ownerDisplayName}:{}},mentions:(e.mentions??[]).map(t=>({nickname:t.displayName,type:t.principalType})),text:e.text??null,attachments:e.attachments.map(t=>({attachmentId:t.attachmentId,mediaKind:t.mediaKind,name:t.name,mimeType:t.mimeType,byteSize:t.byteSize,sha256:t.sha256})),createdAt:e.createdAt}}function S(e){if(!e||Array.isArray(e)||typeof e!="object")throw new o("invalid_params","Structured parameters are required.");return e}function s(e,t,n){if(typeof e!="string"||!e.trim()||e.length>n)throw new o("invalid_params",`${t} is invalid.`);return e.trim()}function b(e){return typeof e=="string"&&e.trim()?e.trim():void 0}function q(e){const t=s(e,"action",32);if(t!=="status"&&t!=="read"&&t!=="send"&&t!=="attachment.get")throw new o("managed_room_action_forbidden","This action is not available in managed Room context.");return t}function _(e,t){if(e==null)return;const n=s(e,t,20);if(!/^(0|[1-9]\d{0,19})$/.test(n))throw new o("invalid_sequence",`${t} is invalid.`);return n}function v(e){const t=e==null?50:Number(e);if(!Number.isInteger(t)||t<1||t>50)throw new o("invalid_limit","limit must be between 1 and 50.");return t}function d(e){return JSON.parse(JSON.stringify(e))}export{z as createManagedRoomIpcHandlers};
@@ -1 +1 @@
1
- import{RoomActionHttpError as m}from"./room-action-client.js";import y from"node:crypto";class b{deps;timer=null;running=!1;started=!1;constructor(i){this.deps=i}start(){this.started||(this.started=!0,this.arm(this.deps.initialDelayMs??0))}stop(){this.started=!1,this.timer&&(this.deps.clearTimeout??clearTimeout)(this.timer),this.timer=null}async runOnce(){if(this.running)return;const i=this.deps.getMachineCredentials().machineToken?.trim();if(i){this.running=!0;try{const n=p(this.deps.registry.list());for(const[r,g]of n)await this.syncRoom(r,g,i)}finally{this.running=!1}}}async syncRoom(i,n,r){const g=n.filter(e=>e.binding&&(e.binding.status==="active"||e.binding.status==="paused"));if(g.length===0)return;let u=0,o=null,s=null;for(const e of g){const d=e.binding,c={machineToken:r,roomId:i,bindingId:d.bindingId};try{const t=await this.deps.actionClient.status(c);if(t.activationCapability==="managed"&&t.managedWorkspacePath&&!this.deps.observations?.getBySource(e)&&this.deps.observations?.observe({machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,hostContextHash:h(e.hostProcessIdentity),workspaceHash:h(t.managedWorkspacePath),workspacePath:t.managedWorkspacePath,lifecycleState:"active",eventName:"ManagedSync"}),this.deps.registry.setBinding({registryKey:e.registryKey,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:t.status,activationMode:t.activationMode}),t.status==="detached"){u+=1;continue}if(t.status==="pending_approval")continue;o??=c,s??={roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:{},agentSystemPromptsByIdentityId:{}},t.agent&&(s.agentsByIdentityId[t.agentIdentityId]=t.agent,s.agentSystemPromptsByIdentityId[t.agentIdentityId]=t.agent.systemPrompt??"")}catch(t){if(l(t)){this.deps.registry.detachBinding(e.registryKey),u+=1;continue}return}}if(o){const e=n.map(d=>this.deps.registry.getByRegistryKey(d.registryKey)??d);await this.pullPages(o,i,e,s);return}}async pullPages(i,n,r,g){const u=this.deps.maxPagesPerRun??100;let o=r.map(s=>s.binding?.lastDeliveredSequence??"0").reduce((s,e)=>BigInt(s)<BigInt(e)?s:e,"0");for(let s=0;s<u;s+=1){const e=await this.deps.actionClient.read({...i,afterSequence:o,limit:100});for(const d of r){const c=this.deps.registry.getByRegistryKey(d.registryKey);c?.binding&&(c.binding.status==="active"||c.binding.status==="paused")&&BigInt(e.nextSequence)>BigInt(c.binding.lastDeliveredSequence)&&this.deps.registry.advanceCursor({registryKey:d.registryKey,cursor:"delivered",sequence:e.nextSequence})}if(this.deps.onMessages&&await this.deps.onMessages(n,e.messages,r,g??{roomName:e.roomName}),!e.hasMore)return;if(e.nextSequence===o)throw new m("room_sync_no_progress","Room mirror synchronization made no progress.");o=e.nextSequence}}arm(i){if(!this.started||this.timer)return;const n=this.deps.setTimeout??setTimeout;this.timer=n(()=>{this.timer=null,this.runOnce().catch(()=>{}).finally(()=>{this.started&&this.arm(this.deps.intervalMs??5e3)})},i),this.timer.unref?.()}}function h(a){return y.createHash("sha256").update(a,"utf8").digest("hex")}function p(a){const i=new Map;for(const n of a){if(!n.binding||n.lifecycleState==="closed")continue;const r=i.get(n.binding.roomId)??[];r.push(n),i.set(n.binding.roomId,r)}return i}function l(a){return a instanceof m&&(a.code==="room_binding_unavailable"||a.code==="room_action_http_403")}export{b as RoomMirrorSyncService};
1
+ import{RoomActionHttpError as m}from"./room-action-client.js";import y from"node:crypto";class b{deps;timer=null;running=!1;started=!1;constructor(i){this.deps=i}start(){this.started||(this.started=!0,this.arm(this.deps.initialDelayMs??0))}stop(){this.started=!1,this.timer&&(this.deps.clearTimeout??clearTimeout)(this.timer),this.timer=null}async runOnce(){if(this.running)return;const i=this.deps.getMachineCredentials().machineToken?.trim();if(i){this.running=!0;try{const n=p(this.deps.registry.list());for(const[r,g]of n)await this.syncRoom(r,g,i)}finally{this.running=!1}}}async syncRoom(i,n,r){const g=n.filter(e=>e.binding&&(e.binding.status==="active"||e.binding.status==="paused"));if(g.length===0)return;let u=0,o=null,s=null;for(const e of g){const d=e.binding,c={machineToken:r,roomId:i,bindingId:d.bindingId};try{const t=await this.deps.actionClient.status(c);if(t.activationCapability==="managed"&&t.managedWorkspacePath&&!this.deps.observations?.getBySource(e)&&this.deps.observations?.observe({machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,hostContextHash:h(e.hostProcessIdentity),workspaceHash:h(t.managedWorkspacePath),workspacePath:t.managedWorkspacePath,lifecycleState:"active",eventName:"ManagedSync"}),this.deps.registry.setBinding({registryKey:e.registryKey,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:t.status,activationMode:t.activationMode}),t.status==="detached"){u+=1;continue}if(t.status==="pending_approval")continue;o??=c,s??={roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:{},agentSystemPromptsByIdentityId:{}},t.agent&&(s.agentsByIdentityId[t.agentIdentityId]=t.agent,s.agentSystemPromptsByIdentityId[t.agentIdentityId]=t.agent.systemPrompt??"")}catch(t){if(l(t)){this.deps.registry.detachBinding(e.registryKey),u+=1;continue}return}}if(o){const e=n.map(d=>this.deps.registry.getByRegistryKey(d.registryKey)??d);await this.pullPages(o,i,e,s);return}}async pullPages(i,n,r,g){const u=this.deps.maxPagesPerRun??100;let o=r.map(s=>s.binding?.lastDeliveredSequence??"0").reduce((s,e)=>BigInt(s)<BigInt(e)?s:e,"0");for(let s=0;s<u;s+=1){const e=await this.deps.actionClient.read({...i,afterSequence:o,limit:100});this.deps.onMessages&&await this.deps.onMessages(n,e.messages,r,g??{roomName:e.roomName});for(const d of r){const c=this.deps.registry.getByRegistryKey(d.registryKey);c?.binding&&(c.binding.status==="active"||c.binding.status==="paused")&&BigInt(e.nextSequence)>BigInt(c.binding.lastDeliveredSequence)&&this.deps.registry.advanceCursor({registryKey:d.registryKey,cursor:"delivered",sequence:e.nextSequence})}if(!e.hasMore)return;if(e.nextSequence===o)throw new m("room_sync_no_progress","Room mirror synchronization made no progress.");o=e.nextSequence}}arm(i){if(!this.started||this.timer)return;const n=this.deps.setTimeout??setTimeout;this.timer=n(()=>{this.timer=null,this.runOnce().catch(()=>{}).finally(()=>{this.started&&this.arm(this.deps.intervalMs??5e3)})},i),this.timer.unref?.()}}function h(a){return y.createHash("sha256").update(a,"utf8").digest("hex")}function p(a){const i=new Map;for(const n of a){if(!n.binding||n.lifecycleState==="closed")continue;const r=i.get(n.binding.roomId)??[];r.push(n),i.set(n.binding.roomId,r)}return i}function l(a){return a instanceof m&&(a.code==="room_binding_unavailable"||a.code==="room_action_http_403")}export{b as RoomMirrorSyncService};
@@ -15,7 +15,9 @@ export type RoomSessionBindingState = {
15
15
  activationMode: RoomActivationMode;
16
16
  lastDeliveredSequence: string;
17
17
  lastActivatedSequence: string;
18
+ /** @deprecated read only for one-time migration into the Session queue. */
18
19
  activationQueue: RoomActivationQueueItem[];
20
+ /** @deprecated read only for clearing pre-unified-scheduler state. */
19
21
  activeActivation: RoomActivationQueueItem | null;
20
22
  mentionContext?: RoomMentionContextItem[];
21
23
  loopBreaker: {
@@ -107,6 +109,8 @@ export declare class RoomSessionRegistry {
107
109
  }): RoomSessionRegistration;
108
110
  enqueueActivation(registryKey: string, item: RoomActivationQueueItem, now?: Date, clearMentionContext?: boolean): RoomSessionRegistration;
109
111
  appendMentionContext(registryKey: string, item: RoomMentionContextItem, now?: Date): RoomSessionRegistration;
112
+ replaceMentionContext(registryKey: string, items: RoomMentionContextItem[], now?: Date): RoomSessionRegistration;
113
+ retireLegacyActivationState(registryKey: string, now?: Date): RoomSessionRegistration;
110
114
  dequeueActivation(registryKey: string, causeMessageId: string, now?: Date): RoomSessionRegistration;
111
115
  markActivationDispatching(registryKey: string, causeMessageId: string, now?: Date): RoomSessionRegistration;
112
116
  clearActiveActivation(registryKey: string, now?: Date): RoomSessionRegistration;
@@ -1,2 +1,2 @@
1
- import w from"node:crypto";import u from"node:fs";import q from"node:path";import{isRoomRuntimeAdapter as C}from"@shennian/wire";import{resolveShennianPath as M}from"../config/index.js";const y=500,A=20,T=/^[A-Za-z0-9_.:@/-]{1,512}$/,I=new Set(["pending_approval","active","paused","detached"]),S=new Set(["auto_follow","mention_only","paused"]);class o extends Error{code;constructor(e,i){super(i),this.code=e,this.name="RoomSessionRegistryError"}}class j{filePath;state;mutexTails=new Map;constructor(e=M("runtime","room-session-registry.json"),i=new Date){this.filePath=e,this.state=this.load(),this.markPersistedActivationsInterrupted(i)&&this.persist()}register(e){c(e.machineId,"machine_id"),f(e.runtimeAdapter),_(e.sourceSessionKey),c(e.nianSessionId,"nian_session_id"),c(e.hostProcessIdentity,"host_process_identity"),P(e.capabilities);const i=v(e),s=(e.now??new Date).toISOString(),n=this.state.records[i];if(Object.values(this.state.records).find(h=>h.registryKey!==i&&h.nianSessionId===e.nianSessionId&&h.lifecycleState!=="closed"))throw new o("nian_session_already_registered","This Shennian session is already registered to another native Agent session.");const r=!!(n&&n.hostProcessIdentity!==e.hostProcessIdentity),l={schemaVersion:1,registryKey:i,machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,panelContextKey:e.nianSessionId,hostProcessIdentity:e.hostProcessIdentity,registrationEpoch:n?n.registrationEpoch+(r?1:0):1,lifecycleState:"active",capabilities:{...e.capabilities},binding:n?.binding??null,registeredAt:n?.registeredAt??s,lastSeenAt:s,updatedAt:s};return this.state.records[i]=l,this.persist(),d(l)}getBySource(e){const i=this.state.records[v(e)];return i?d(i):null}getByNianSessionId(e){c(e,"nian_session_id");const i=Object.values(this.state.records).find(s=>s.nianSessionId===e&&s.lifecycleState!=="closed");return i?d(i):null}getByRegistryKey(e){if(!/^[a-f0-9]{64}$/.test(e))throw new o("invalid_registry_key","Invalid Room session registry key.");const i=this.state.records[e];return i?d(i):null}list(){return Object.values(this.state.records).sort((e,i)=>e.registeredAt.localeCompare(i.registeredAt)).map(d)}setBinding(e){const i=this.requireRecord(e.registryKey);for(const[a,r]of Object.entries({binding_id:e.bindingId,room_id:e.roomId,agent_identity_id:e.agentIdentityId}))c(r,a);if(!I.has(e.status))throw new o("invalid_binding_status","Invalid binding status.");if(e.status==="pending_approval"&&e.agentMembershipId!==null||(e.status==="active"||e.status==="paused")&&e.agentMembershipId===null)throw new o("invalid_agent_membership_id","Pending Room bindings cannot claim an Agent membership.");e.agentMembershipId!==null&&c(e.agentMembershipId,"agent_membership_id");const s=e.activationMode??"auto_follow";if(!S.has(s))throw new o("invalid_activation_mode","Invalid activation mode.");if(i.binding&&i.binding.status!=="detached"&&(i.binding.bindingId!==e.bindingId||i.binding.roomId!==e.roomId))throw new o("session_already_bound","An Agent session can have only one active Room binding.");const n=i.binding?.activationMode;return i.binding={bindingId:e.bindingId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,agentMembershipId:e.agentMembershipId,status:e.status,activationMode:s,lastDeliveredSequence:i.binding?.lastDeliveredSequence??"0",lastActivatedSequence:i.binding?.lastActivatedSequence??"0",activationQueue:i.binding?.activationQueue??[],activeActivation:i.binding?.activeActivation??null,mentionContext:n==="mention_only"&&s==="mention_only"?i.binding?.mentionContext??[]:[],loopBreaker:i.binding?.loopBreaker??{windowStartedAt:null,activationCount:0,pausedUntil:null}},this.touchAndPersist(i,e.now),d(i)}detachBinding(e,i=new Date){const s=this.requireRecord(e);return s.binding&&(s.binding.status="detached",s.binding.activationMode="paused",s.binding.activationQueue=[],s.binding.mentionContext=[]),this.touchAndPersist(s,i),d(s)}advanceCursor(e){const i=this.requireBoundRecord(e.registryKey);p(e.sequence);const s=e.cursor==="delivered"?"lastDeliveredSequence":"lastActivatedSequence";if(BigInt(e.sequence)<BigInt(i.binding[s]))throw new o("cursor_regression","Room cursor cannot move backwards.");return i.binding[s]=e.sequence,this.touchAndPersist(i,e.now),d(i)}enqueueActivation(e,i,s=new Date,n=!1){const a=this.requireBoundRecord(e);m(i);const r=a.binding.activationQueue;if(!r.some(l=>l.causeMessageId===i.causeMessageId)){if(r.length>=y)throw new o("activation_queue_full","Agent activation queue is full.");r.push(d(i))}return n&&(a.binding.mentionContext=[]),this.touchAndPersist(a,s),d(a)}appendMentionContext(e,i,s=new Date){const n=this.requireBoundRecord(e);b(i);const a=n.binding.mentionContext??[];return a.some(r=>r.sequence===i.sequence)||(a.push(d(i)),n.binding.mentionContext=a.slice(-A)),this.touchAndPersist(n,s),d(n)}dequeueActivation(e,i,s=new Date){const n=this.requireBoundRecord(e);return n.binding.activationQueue=n.binding.activationQueue.filter(a=>a.causeMessageId!==i),this.touchAndPersist(n,s),d(n)}markActivationDispatching(e,i,s=new Date){const n=this.requireBoundRecord(e),a=n.binding.activationQueue.find(r=>r.causeMessageId===i);if(!a)throw new o("activation_not_found","Agent activation is unavailable.");return n.binding.activeActivation={...d(a),dispatchState:"dispatching",dispatchedAt:s.toISOString(),interruptedAt:void 0},n.binding.activationQueue=n.binding.activationQueue.filter(r=>r.causeMessageId!==i),this.touchAndPersist(n,s),d(n)}clearActiveActivation(e,i=new Date){const s=this.requireBoundRecord(e);return s.binding.activeActivation=null,this.touchAndPersist(s,i),d(s)}retryActiveActivation(e){const i=this.requireBoundRecord(e.registryKey),s=i.binding.activeActivation;if(!s)return{queued:!1,attempts:0};if(x(s))throw new o("activation_interrupted_requires_explicit_retry","Interrupted Agent activation requires an explicit retry.");return this.requeueActiveActivation(i,e)}requeueActiveActivation(e,i){const s=e.binding.activeActivation,n=i.now??new Date,{dispatchState:a,dispatchedAt:r,interruptedAt:l,...h}=s,g={...h,attempts:s.attempts+1};return e.binding.activeActivation=null,g.attempts<i.maxAttempts?(e.binding.activationQueue.some(R=>R.causeMessageId===g.causeMessageId)||e.binding.activationQueue.unshift(g),this.touchAndPersist(e,n),{queued:!0,attempts:g.attempts}):(e.binding.loopBreaker.pausedUntil=new Date(n.getTime()+i.pauseMs).toISOString(),this.touchAndPersist(e,n),{queued:!1,attempts:g.attempts})}markPersistedActivationsInterrupted(e){const i=e.toISOString();let s=!1;for(const n of Object.values(this.state.records)){const a=n.binding?.activeActivation;!a||x(a)||(a.dispatchState="interrupted",a.interruptedAt=i,n.updatedAt=i,s=!0)}return s}claimActivation(e){const i=this.requireBoundRecord(e.registryKey),s=e.now??new Date,n=i.binding.loopBreaker;return n.pausedUntil&&Date.parse(n.pausedUntil)>s.getTime()?{allowed:!1,pausedUntil:n.pausedUntil,newlyPaused:!1}:((!n.windowStartedAt||s.getTime()-Date.parse(n.windowStartedAt)>=e.windowMs)&&(n.windowStartedAt=s.toISOString(),n.activationCount=0,n.pausedUntil=null),n.activationCount>=e.maxActivations?(n.pausedUntil=new Date(s.getTime()+e.pauseMs).toISOString(),this.touchAndPersist(i,s),{allowed:!1,pausedUntil:n.pausedUntil,newlyPaused:!0}):(n.activationCount+=1,this.touchAndPersist(i,s),{allowed:!0,pausedUntil:null}))}updateLifecycle(e,i,s=new Date){const n=this.requireRecord(e);return n.lifecycleState=i,i==="closed"&&n.binding&&(n.binding.status="detached",n.binding.activationMode="paused",n.binding.activationQueue=[],n.binding.mentionContext=[]),this.touchAndPersist(n,s),d(n)}async runExclusive(e,i){this.requireRecord(e);const s=this.mutexTails.get(e)??Promise.resolve();let n;const a=new Promise(l=>{n=l}),r=s.then(()=>a);this.mutexTails.set(e,r),await s;try{return await i()}finally{n(),this.mutexTails.get(e)===r&&this.mutexTails.delete(e)}}requireRecord(e){const i=this.state.records[e];if(!i)throw new o("session_registration_not_found","Agent session is not registered.");return i}requireBoundRecord(e){const i=this.requireRecord(e);if(!i.binding||i.binding.status!=="active"&&i.binding.status!=="paused")throw new o("session_not_bound","Agent session has no active Room binding.");return i}touchAndPersist(e,i=new Date){e.updatedAt=i.toISOString(),this.persist()}load(){try{const e=u.lstatSync(this.filePath);if(!e.isFile()||e.isSymbolicLink())throw new o("unsafe_session_registry","Room session registry is not a regular file.");process.platform!=="win32"&&(e.mode&63)!==0&&u.chmodSync(this.filePath,384);const i=JSON.parse(u.readFileSync(this.filePath,"utf8"));return D(i)}catch(e){if(k(e))return{schemaVersion:1,records:{}};throw e instanceof o?e:new o("corrupt_session_registry","Room Agent session registry is corrupt.")}}persist(){const e=q.dirname(this.filePath);u.mkdirSync(e,{recursive:!0,mode:448}),process.platform!=="win32"&&u.chmodSync(e,448);const i=`${this.filePath}.tmp-${process.pid}-${w.randomBytes(6).toString("hex")}`;try{if(u.writeFileSync(i,`${JSON.stringify(this.state,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&u.chmodSync(i,384),process.platform==="win32"&&u.existsSync(this.filePath)){const s=u.lstatSync(this.filePath);if(!s.isFile()||s.isSymbolicLink())throw new o("unsafe_session_registry","Refusing to replace an unsafe Room session registry.");u.unlinkSync(this.filePath)}u.renameSync(i,this.filePath),process.platform!=="win32"&&u.chmodSync(this.filePath,384)}finally{try{u.unlinkSync(i)}catch{}}}}function v(t){return c(t.machineId,"machine_id"),f(t.runtimeAdapter),_(t.sourceSessionKey),w.createHash("sha256").update(`${t.machineId}\0${t.runtimeAdapter}\0${t.sourceSessionKey}`,"utf8").digest("hex")}function D(t){if(!t||typeof t!="object"||t.schemaVersion!==1)throw new o("corrupt_session_registry","Unsupported Room session registry.");const e=t.records;if(!e||typeof e!="object"||Array.isArray(e))throw new o("corrupt_session_registry","Room session records are invalid.");const i={};for(const[s,n]of Object.entries(e)){if(B(n),s!==n.registryKey||s!==v(n))throw new o("corrupt_session_registry","Room session registry key mismatch.");i[s]={...n,binding:n.binding?{...n.binding,activeActivation:n.binding.activeActivation??null,mentionContext:n.binding.mentionContext??[]}:null}}return{schemaVersion:1,records:i}}function B(t){if(!t||t.schemaVersion!==1)throw new o("corrupt_session_registry","Invalid registration.");if(c(t.machineId,"machine_id"),f(t.runtimeAdapter),_(t.sourceSessionKey),c(t.nianSessionId,"nian_session_id"),c(t.hostProcessIdentity,"host_process_identity"),t.panelContextKey!==t.nianSessionId)throw new o("corrupt_session_registry","Invalid panel context.");if(!Number.isInteger(t.registrationEpoch)||t.registrationEpoch<1)throw new o("corrupt_session_registry","Invalid registration epoch.");P(t.capabilities),t.binding&&O(t.binding)}function O(t){if(c(t.bindingId,"binding_id"),c(t.roomId,"room_id"),c(t.agentIdentityId,"agent_identity_id"),!I.has(t.status))throw new o("corrupt_session_registry","Invalid binding status.");if(t.status==="pending_approval"&&t.agentMembershipId!==null||(t.status==="active"||t.status==="paused")&&t.agentMembershipId===null)throw new o("corrupt_session_registry","Invalid Agent membership.");if(t.agentMembershipId!==null&&c(t.agentMembershipId,"agent_membership_id"),!S.has(t.activationMode))throw new o("corrupt_session_registry","Invalid activation mode.");if(p(t.lastDeliveredSequence),p(t.lastActivatedSequence),!Array.isArray(t.activationQueue)||t.activationQueue.length>y)throw new o("corrupt_session_registry","Invalid activation queue.");if(t.activationQueue.forEach(m),t.activeActivation!=null&&m(t.activeActivation),t.mentionContext!==void 0&&(!Array.isArray(t.mentionContext)||t.mentionContext.length>A))throw new o("corrupt_session_registry","Invalid mention context.");t.mentionContext?.forEach(b)}function b(t){if(p(t.sequence),typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3)throw new o("invalid_mention_context","Invalid mention context.")}function m(t){if(c(t.causeMessageId,"cause_message_id"),c(t.rootCauseMessageId,"root_cause_message_id"),p(t.sequence),!Number.isInteger(t.hopCount)||t.hopCount<0||t.hopCount>32)throw new o("invalid_hop_count","Invalid Agent activation hop count.");if(!Number.isInteger(t.attempts)||t.attempts<0||t.attempts>100)throw new o("invalid_activation_attempts","Invalid Agent activation attempts.");if(!Number.isFinite(new Date(t.enqueuedAt).getTime()))throw new o("invalid_activation_time","Invalid Agent activation time.");if(t.deliveryText!==void 0&&(typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3))throw new o("invalid_activation_text","Invalid Agent activation text.");if(t.systemPrompt!==void 0&&(typeof t.systemPrompt!="string"||!t.systemPrompt.trim()||t.systemPrompt.length>24e3))throw new o("invalid_activation_prompt","Invalid Agent activation prompt.");if(t.promptSnapshot!==void 0&&!E(t.promptSnapshot))throw new o("invalid_activation_prompt_snapshot","Invalid Agent activation prompt snapshot.");if(t.dispatchState!==void 0&&t.dispatchState!=="dispatching"&&t.dispatchState!=="interrupted")throw new o("invalid_activation_state","Invalid Agent activation state.");if(t.dispatchedAt!==void 0&&!Number.isFinite(new Date(t.dispatchedAt).getTime()))throw new o("invalid_activation_dispatch_time","Invalid Agent activation dispatch time.");if(t.dispatchState===void 0&&t.dispatchedAt!==void 0)throw new o("invalid_activation_dispatch_time","Queued Agent activation cannot have a dispatch time.");if(t.dispatchState==="interrupted"&&(t.interruptedAt===void 0||!Number.isFinite(new Date(t.interruptedAt).getTime())))throw new o("invalid_activation_interrupted_time","Invalid Agent activation interruption time.");if(t.dispatchState!=="interrupted"&&t.interruptedAt!==void 0)throw new o("invalid_activation_interrupted_time","Unexpected Agent activation interruption time.")}function E(t){if(!t||typeof t!="object"||Array.isArray(t))return!1;const e=t,i=s=>s===null||typeof s=="string"&&/^[a-f0-9]{64}$/.test(s);return typeof e.managedRoomPolicyVersion=="string"&&e.managedRoomPolicyVersion.length<=64&&i(e.sessionModeInstructionsHash)&&i(e.agentSystemPromptHash)&&typeof e.trustedRoomContextHash=="string"&&/^[a-f0-9]{64}$/.test(e.trustedRoomContextHash)}function x(t){return t.dispatchState==="interrupted"}function P(t){if(!t||["canIdentifySession","canBindAutomatically","canOpenEmbeddedRoomPage","canResumeIdleSession","canActivateWhileHostOpen"].some(i=>typeof t[i]!="boolean"))throw new o("invalid_session_capabilities","Invalid Agent session capabilities.")}function f(t){if(!C(t))throw new o("invalid_runtime_adapter","Unsupported Room runtime adapter.")}function _(t){if(!t||t.length>1024||/[\0\r\n]/.test(t))throw new o("invalid_source_session_key","Invalid native Agent session key.")}function c(t,e){if(!T.test(t))throw new o(`invalid_${e}`,`Invalid ${e}.`)}function p(t){if(!/^(0|[1-9]\d{0,19})$/.test(t))throw new o("invalid_room_sequence","Invalid Room sequence.")}function d(t){return structuredClone(t)}function k(t){return typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT"}export{j as RoomSessionRegistry,o as RoomSessionRegistryError,v as buildRoomSessionRegistryKey,x as isInterruptedRoomActivation};
1
+ import A from"node:crypto";import u from"node:fs";import q from"node:path";import{isRoomRuntimeAdapter as C}from"@shennian/wire";import{resolveShennianPath as M}from"../config/index.js";const I=500,p=20,T=/^[A-Za-z0-9_.:@/-]{1,512}$/,b=new Set(["pending_approval","active","paused","detached"]),S=new Set(["auto_follow","mention_only","paused"]);class o extends Error{code;constructor(e,i){super(i),this.code=e,this.name="RoomSessionRegistryError"}}class j{filePath;state;mutexTails=new Map;constructor(e=M("runtime","room-session-registry.json"),i=new Date){this.filePath=e,this.state=this.load(),this.markPersistedActivationsInterrupted(i)&&this.persist()}register(e){c(e.machineId,"machine_id"),_(e.runtimeAdapter),y(e.sourceSessionKey),c(e.nianSessionId,"nian_session_id"),c(e.hostProcessIdentity,"host_process_identity"),P(e.capabilities);const i=m(e),n=(e.now??new Date).toISOString(),s=this.state.records[i];if(Object.values(this.state.records).find(h=>h.registryKey!==i&&h.nianSessionId===e.nianSessionId&&h.lifecycleState!=="closed"))throw new o("nian_session_already_registered","This Shennian session is already registered to another native Agent session.");const d=!!(s&&s.hostProcessIdentity!==e.hostProcessIdentity),l={schemaVersion:1,registryKey:i,machineId:e.machineId,runtimeAdapter:e.runtimeAdapter,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,panelContextKey:e.nianSessionId,hostProcessIdentity:e.hostProcessIdentity,registrationEpoch:s?s.registrationEpoch+(d?1:0):1,lifecycleState:"active",capabilities:{...e.capabilities},binding:s?.binding??null,registeredAt:s?.registeredAt??n,lastSeenAt:n,updatedAt:n};return this.state.records[i]=l,this.persist(),r(l)}getBySource(e){const i=this.state.records[m(e)];return i?r(i):null}getByNianSessionId(e){c(e,"nian_session_id");const i=Object.values(this.state.records).find(n=>n.nianSessionId===e&&n.lifecycleState!=="closed");return i?r(i):null}getByRegistryKey(e){if(!/^[a-f0-9]{64}$/.test(e))throw new o("invalid_registry_key","Invalid Room session registry key.");const i=this.state.records[e];return i?r(i):null}list(){return Object.values(this.state.records).sort((e,i)=>e.registeredAt.localeCompare(i.registeredAt)).map(r)}setBinding(e){const i=this.requireRecord(e.registryKey);for(const[a,d]of Object.entries({binding_id:e.bindingId,room_id:e.roomId,agent_identity_id:e.agentIdentityId}))c(d,a);if(!b.has(e.status))throw new o("invalid_binding_status","Invalid binding status.");if(e.status==="pending_approval"&&e.agentMembershipId!==null||(e.status==="active"||e.status==="paused")&&e.agentMembershipId===null)throw new o("invalid_agent_membership_id","Pending Room bindings cannot claim an Agent membership.");e.agentMembershipId!==null&&c(e.agentMembershipId,"agent_membership_id");const n=e.activationMode??"auto_follow";if(!S.has(n))throw new o("invalid_activation_mode","Invalid activation mode.");if(i.binding&&i.binding.status!=="detached"&&(i.binding.bindingId!==e.bindingId||i.binding.roomId!==e.roomId))throw new o("session_already_bound","An Agent session can have only one active Room binding.");const s=i.binding?.activationMode;return i.binding={bindingId:e.bindingId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,agentMembershipId:e.agentMembershipId,status:e.status,activationMode:n,lastDeliveredSequence:i.binding?.lastDeliveredSequence??"0",lastActivatedSequence:i.binding?.lastActivatedSequence??"0",activationQueue:i.binding?.activationQueue??[],activeActivation:i.binding?.activeActivation??null,mentionContext:s==="mention_only"&&n==="mention_only"?i.binding?.mentionContext??[]:[],loopBreaker:i.binding?.loopBreaker??{windowStartedAt:null,activationCount:0,pausedUntil:null}},this.touchAndPersist(i,e.now),r(i)}detachBinding(e,i=new Date){const n=this.requireRecord(e);return n.binding&&(n.binding.status="detached",n.binding.activationMode="paused",n.binding.activationQueue=[],n.binding.mentionContext=[]),this.touchAndPersist(n,i),r(n)}advanceCursor(e){const i=this.requireBoundRecord(e.registryKey);v(e.sequence);const n=e.cursor==="delivered"?"lastDeliveredSequence":"lastActivatedSequence";if(BigInt(e.sequence)<BigInt(i.binding[n]))throw new o("cursor_regression","Room cursor cannot move backwards.");return i.binding[n]=e.sequence,this.touchAndPersist(i,e.now),r(i)}enqueueActivation(e,i,n=new Date,s=!1){const a=this.requireBoundRecord(e);w(i);const d=a.binding.activationQueue;if(!d.some(l=>l.causeMessageId===i.causeMessageId)){if(d.length>=I)throw new o("activation_queue_full","Agent activation queue is full.");d.push(r(i))}return s&&(a.binding.mentionContext=[]),this.touchAndPersist(a,n),r(a)}appendMentionContext(e,i,n=new Date){const s=this.requireBoundRecord(e);f(i);const a=s.binding.mentionContext??[];return a.some(d=>d.sequence===i.sequence)||(a.push(r(i)),s.binding.mentionContext=a.slice(-p)),this.touchAndPersist(s,n),r(s)}replaceMentionContext(e,i,n=new Date){const s=this.requireBoundRecord(e);for(const a of i)f(a);return s.binding.mentionContext=i.slice(-p).map(r),this.touchAndPersist(s,n),r(s)}retireLegacyActivationState(e,i=new Date){const n=this.requireBoundRecord(e);return n.binding.activationQueue=[],n.binding.activeActivation=null,this.touchAndPersist(n,i),r(n)}dequeueActivation(e,i,n=new Date){const s=this.requireBoundRecord(e);return s.binding.activationQueue=s.binding.activationQueue.filter(a=>a.causeMessageId!==i),this.touchAndPersist(s,n),r(s)}markActivationDispatching(e,i,n=new Date){const s=this.requireBoundRecord(e),a=s.binding.activationQueue.find(d=>d.causeMessageId===i);if(!a)throw new o("activation_not_found","Agent activation is unavailable.");return s.binding.activeActivation={...r(a),dispatchState:"dispatching",dispatchedAt:n.toISOString(),interruptedAt:void 0},s.binding.activationQueue=s.binding.activationQueue.filter(d=>d.causeMessageId!==i),this.touchAndPersist(s,n),r(s)}clearActiveActivation(e,i=new Date){const n=this.requireBoundRecord(e);return n.binding.activeActivation=null,this.touchAndPersist(n,i),r(n)}retryActiveActivation(e){const i=this.requireBoundRecord(e.registryKey),n=i.binding.activeActivation;if(!n)return{queued:!1,attempts:0};if(x(n))throw new o("activation_interrupted_requires_explicit_retry","Interrupted Agent activation requires an explicit retry.");return this.requeueActiveActivation(i,e)}requeueActiveActivation(e,i){const n=e.binding.activeActivation,s=i.now??new Date,{dispatchState:a,dispatchedAt:d,interruptedAt:l,...h}=n,g={...h,attempts:n.attempts+1};return e.binding.activeActivation=null,g.attempts<i.maxAttempts?(e.binding.activationQueue.some(R=>R.causeMessageId===g.causeMessageId)||e.binding.activationQueue.unshift(g),this.touchAndPersist(e,s),{queued:!0,attempts:g.attempts}):(e.binding.loopBreaker.pausedUntil=new Date(s.getTime()+i.pauseMs).toISOString(),this.touchAndPersist(e,s),{queued:!1,attempts:g.attempts})}markPersistedActivationsInterrupted(e){const i=e.toISOString();let n=!1;for(const s of Object.values(this.state.records)){const a=s.binding?.activeActivation;!a||x(a)||(a.dispatchState="interrupted",a.interruptedAt=i,s.updatedAt=i,n=!0)}return n}claimActivation(e){const i=this.requireBoundRecord(e.registryKey),n=e.now??new Date,s=i.binding.loopBreaker;return s.pausedUntil&&Date.parse(s.pausedUntil)>n.getTime()?{allowed:!1,pausedUntil:s.pausedUntil,newlyPaused:!1}:((!s.windowStartedAt||n.getTime()-Date.parse(s.windowStartedAt)>=e.windowMs)&&(s.windowStartedAt=n.toISOString(),s.activationCount=0,s.pausedUntil=null),s.activationCount>=e.maxActivations?(s.pausedUntil=new Date(n.getTime()+e.pauseMs).toISOString(),this.touchAndPersist(i,n),{allowed:!1,pausedUntil:s.pausedUntil,newlyPaused:!0}):(s.activationCount+=1,this.touchAndPersist(i,n),{allowed:!0,pausedUntil:null}))}updateLifecycle(e,i,n=new Date){const s=this.requireRecord(e);return s.lifecycleState=i,i==="closed"&&s.binding&&(s.binding.status="detached",s.binding.activationMode="paused",s.binding.activationQueue=[],s.binding.mentionContext=[]),this.touchAndPersist(s,n),r(s)}async runExclusive(e,i){this.requireRecord(e);const n=this.mutexTails.get(e)??Promise.resolve();let s;const a=new Promise(l=>{s=l}),d=n.then(()=>a);this.mutexTails.set(e,d),await n;try{return await i()}finally{s(),this.mutexTails.get(e)===d&&this.mutexTails.delete(e)}}requireRecord(e){const i=this.state.records[e];if(!i)throw new o("session_registration_not_found","Agent session is not registered.");return i}requireBoundRecord(e){const i=this.requireRecord(e);if(!i.binding||i.binding.status!=="active"&&i.binding.status!=="paused")throw new o("session_not_bound","Agent session has no active Room binding.");return i}touchAndPersist(e,i=new Date){e.updatedAt=i.toISOString(),this.persist()}load(){try{const e=u.lstatSync(this.filePath);if(!e.isFile()||e.isSymbolicLink())throw new o("unsafe_session_registry","Room session registry is not a regular file.");process.platform!=="win32"&&(e.mode&63)!==0&&u.chmodSync(this.filePath,384);const i=JSON.parse(u.readFileSync(this.filePath,"utf8"));return D(i)}catch(e){if(k(e))return{schemaVersion:1,records:{}};throw e instanceof o?e:new o("corrupt_session_registry","Room Agent session registry is corrupt.")}}persist(){const e=q.dirname(this.filePath);u.mkdirSync(e,{recursive:!0,mode:448}),process.platform!=="win32"&&u.chmodSync(e,448);const i=`${this.filePath}.tmp-${process.pid}-${A.randomBytes(6).toString("hex")}`;try{if(u.writeFileSync(i,`${JSON.stringify(this.state,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&u.chmodSync(i,384),process.platform==="win32"&&u.existsSync(this.filePath)){const n=u.lstatSync(this.filePath);if(!n.isFile()||n.isSymbolicLink())throw new o("unsafe_session_registry","Refusing to replace an unsafe Room session registry.");u.unlinkSync(this.filePath)}u.renameSync(i,this.filePath),process.platform!=="win32"&&u.chmodSync(this.filePath,384)}finally{try{u.unlinkSync(i)}catch{}}}}function m(t){return c(t.machineId,"machine_id"),_(t.runtimeAdapter),y(t.sourceSessionKey),A.createHash("sha256").update(`${t.machineId}\0${t.runtimeAdapter}\0${t.sourceSessionKey}`,"utf8").digest("hex")}function D(t){if(!t||typeof t!="object"||t.schemaVersion!==1)throw new o("corrupt_session_registry","Unsupported Room session registry.");const e=t.records;if(!e||typeof e!="object"||Array.isArray(e))throw new o("corrupt_session_registry","Room session records are invalid.");const i={};for(const[n,s]of Object.entries(e)){if(B(s),n!==s.registryKey||n!==m(s))throw new o("corrupt_session_registry","Room session registry key mismatch.");i[n]={...s,binding:s.binding?{...s.binding,activeActivation:s.binding.activeActivation??null,mentionContext:s.binding.mentionContext??[]}:null}}return{schemaVersion:1,records:i}}function B(t){if(!t||t.schemaVersion!==1)throw new o("corrupt_session_registry","Invalid registration.");if(c(t.machineId,"machine_id"),_(t.runtimeAdapter),y(t.sourceSessionKey),c(t.nianSessionId,"nian_session_id"),c(t.hostProcessIdentity,"host_process_identity"),t.panelContextKey!==t.nianSessionId)throw new o("corrupt_session_registry","Invalid panel context.");if(!Number.isInteger(t.registrationEpoch)||t.registrationEpoch<1)throw new o("corrupt_session_registry","Invalid registration epoch.");P(t.capabilities),t.binding&&O(t.binding)}function O(t){if(c(t.bindingId,"binding_id"),c(t.roomId,"room_id"),c(t.agentIdentityId,"agent_identity_id"),!b.has(t.status))throw new o("corrupt_session_registry","Invalid binding status.");if(t.status==="pending_approval"&&t.agentMembershipId!==null||(t.status==="active"||t.status==="paused")&&t.agentMembershipId===null)throw new o("corrupt_session_registry","Invalid Agent membership.");if(t.agentMembershipId!==null&&c(t.agentMembershipId,"agent_membership_id"),!S.has(t.activationMode))throw new o("corrupt_session_registry","Invalid activation mode.");if(v(t.lastDeliveredSequence),v(t.lastActivatedSequence),!Array.isArray(t.activationQueue)||t.activationQueue.length>I)throw new o("corrupt_session_registry","Invalid activation queue.");if(t.activationQueue.forEach(w),t.activeActivation!=null&&w(t.activeActivation),t.mentionContext!==void 0&&(!Array.isArray(t.mentionContext)||t.mentionContext.length>p))throw new o("corrupt_session_registry","Invalid mention context.");t.mentionContext?.forEach(f)}function f(t){if(v(t.sequence),typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3)throw new o("invalid_mention_context","Invalid mention context.")}function w(t){if(c(t.causeMessageId,"cause_message_id"),c(t.rootCauseMessageId,"root_cause_message_id"),v(t.sequence),!Number.isInteger(t.hopCount)||t.hopCount<0||t.hopCount>32)throw new o("invalid_hop_count","Invalid Agent activation hop count.");if(!Number.isInteger(t.attempts)||t.attempts<0||t.attempts>100)throw new o("invalid_activation_attempts","Invalid Agent activation attempts.");if(!Number.isFinite(new Date(t.enqueuedAt).getTime()))throw new o("invalid_activation_time","Invalid Agent activation time.");if(t.deliveryText!==void 0&&(typeof t.deliveryText!="string"||!t.deliveryText.trim()||t.deliveryText.length>24e3))throw new o("invalid_activation_text","Invalid Agent activation text.");if(t.systemPrompt!==void 0&&(typeof t.systemPrompt!="string"||!t.systemPrompt.trim()||t.systemPrompt.length>24e3))throw new o("invalid_activation_prompt","Invalid Agent activation prompt.");if(t.promptSnapshot!==void 0&&!E(t.promptSnapshot))throw new o("invalid_activation_prompt_snapshot","Invalid Agent activation prompt snapshot.");if(t.dispatchState!==void 0&&t.dispatchState!=="dispatching"&&t.dispatchState!=="interrupted")throw new o("invalid_activation_state","Invalid Agent activation state.");if(t.dispatchedAt!==void 0&&!Number.isFinite(new Date(t.dispatchedAt).getTime()))throw new o("invalid_activation_dispatch_time","Invalid Agent activation dispatch time.");if(t.dispatchState===void 0&&t.dispatchedAt!==void 0)throw new o("invalid_activation_dispatch_time","Queued Agent activation cannot have a dispatch time.");if(t.dispatchState==="interrupted"&&(t.interruptedAt===void 0||!Number.isFinite(new Date(t.interruptedAt).getTime())))throw new o("invalid_activation_interrupted_time","Invalid Agent activation interruption time.");if(t.dispatchState!=="interrupted"&&t.interruptedAt!==void 0)throw new o("invalid_activation_interrupted_time","Unexpected Agent activation interruption time.")}function E(t){if(!t||typeof t!="object"||Array.isArray(t))return!1;const e=t,i=n=>n===null||typeof n=="string"&&/^[a-f0-9]{64}$/.test(n);return typeof e.managedRoomPolicyVersion=="string"&&e.managedRoomPolicyVersion.length<=64&&i(e.sessionModeInstructionsHash)&&i(e.agentSystemPromptHash)&&typeof e.trustedRoomContextHash=="string"&&/^[a-f0-9]{64}$/.test(e.trustedRoomContextHash)}function x(t){return t.dispatchState==="interrupted"}function P(t){if(!t||["canIdentifySession","canBindAutomatically","canOpenEmbeddedRoomPage","canResumeIdleSession","canActivateWhileHostOpen"].some(i=>typeof t[i]!="boolean"))throw new o("invalid_session_capabilities","Invalid Agent session capabilities.")}function _(t){if(!C(t))throw new o("invalid_runtime_adapter","Unsupported Room runtime adapter.")}function y(t){if(!t||t.length>1024||/[\0\r\n]/.test(t))throw new o("invalid_source_session_key","Invalid native Agent session key.")}function c(t,e){if(!T.test(t))throw new o(`invalid_${e}`,`Invalid ${e}.`)}function v(t){if(!/^(0|[1-9]\d{0,19})$/.test(t))throw new o("invalid_room_sequence","Invalid Room sequence.")}function r(t){return structuredClone(t)}function k(t){return typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT"}export{j as RoomSessionRegistry,o as RoomSessionRegistryError,m as buildRoomSessionRegistryKey,x as isInterruptedRoomActivation};
@@ -1,2 +1,2 @@
1
- import j from"node:os";import{createHash as le}from"node:crypto";import{createAgent as ce}from"../../agents/adapter.js";import{buildApprovalPendingPayload as ue,buildUserMessagePayload as ge}from"@shennian/wire";import{ManagerModeAdapter as fe}from"../../agents/manager.js";import{reportLog as b}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as pe}from"../../native-fusion/parsers.js";import{appendMessage as q,getDaemonInstallationId as K,getMessageDeliveryStatus as me,recordSession as P,upsertMessage as z}from"../store.js";import{mergeProjectedSessions as Ie}from"../projection.js";import{buildManagedAgentEnv as he}from"../../agents/config-status.js";import{parsePersonalAttachmentReferences as ye}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as Se,markAttachmentReferences as Ae}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as E}from"../index-publisher.js";import{writeToolDetail as we}from"../tool-detail-store.js";import{getPersonalSyncIdentity as G}from"../../personal-sync/epoch.js";function Ee(e){const n=e.indexOf(`
2
- `),s=n>0?Math.min(n,80):Math.min(e.length,80);return e.slice(0,s)}function X(e,n,s){return`${e}:${le("sha256").update(`${n}\0${s??""}`).digest("hex")}`}function xe(e,n,s={}){if(e.state==="tool-call"||e.state==="tool-result"){const a={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:n,detailRef:a,status:e.state==="tool-call"?"running":"completed",detailAvailable:!0,...e.durationMs!==void 0?{durationMs:e.durationMs}:{},...e.name?{name:e.name}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...s}}return{...e,sessionId:n,...s}}const Re=3e4;function ke(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function Y(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function Z(e,n){return e!=="manager"?e:n==="claude"?"claude":"codex"}function Me(e,n){P({sessionId:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:n.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,workDir:n.workDir,status:"active",externalChannel:null}}})}function ee(e){return he(e)}function te(e,n,s){return{}}function ne(e,n,s,a,i,c){e.configure?.({sessionId:n,externalChannel:a??null,env:{...ee(s),...te(n,a,i),...c?{SHENNIAN_MANAGED_ROOM_CONTEXT:c}:{}}})}function Te(e,n,s){if(e!=="claude"||!s)return n;const a=n.trim();return!!a&&a.startsWith("-")&&!a.includes("/")?pe(s)??n:n}function be(e,n,s,a){let i=null;function c(t,r={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:xe(t,n,r),seq:t.seq,id:`agent-evt-${t.runId}-${t.seq}`})}function I(t){t?.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null)}function S(t){if(!t.currentRunId||!t.currentRunPhase)return;const r=t.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:n,runId:t.currentRunId,seq:r,runPhase:t.currentRunPhase},seq:r,id:`agent-heartbeat-${t.currentRunId}-${r}-${Date.now()}`})}function x(t){!t||t.heartbeatTimer||(t.heartbeatTimer=setInterval(()=>{S(t)},Re),t.heartbeatTimer.unref?.())}function l(t){const r=t?.pendingTextEvent;!t||!r||!r.text||(q(n,{id:`agent-${r.runId}-${r.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:r.text}),E(e.client),c({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),t.pendingTextEvent=null)}a.on("agentEvent",t=>{const r=e.sessions.get(n),T=ke(t),R=t.state==="final"||t.state==="error"||t.state==="aborted";if(r&&(r.nextEventSeq=t.seq+1,R||(r.currentRunId=t.runId),!R&&T&&(r.currentRunPhase=T,x(r)),t.agentSessionId&&(r.agentSessionId=t.agentSessionId)),e.managerRuntime?.noteAgentEvent(n,t),t.state==="delta"&&t.thinking)return;t.state!=="delta"&&b({level:"info",sessionId:n,wsEvent:`agent.${t.state}`,wsDirection:"out",metadata:{runId:t.runId,seq:t.seq,agentType:s}});let A=!1;if(t.state==="tool-call"||t.state==="tool-result"){const m=`agent-${t.runId}-${t.seq}`;we({sessionId:n,toolCallId:m,detailRevision:t.seq,ts:Date.now(),name:t.name??"tool",...t.args!==void 0?{args:t.args}:{},...t.result!==void 0?{result:t.result}:{},status:t.state==="tool-call"?"running":"completed",...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq}}),q(n,{id:m,sessionId:n,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:t.state==="tool-call"?"tool_use":"tool_result",name:t.name,toolCallId:m,status:t.state==="tool-call"?"running":"completed",...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq},detailRevision:t.seq,detailAvailable:!0,resultOmitted:t.state==="tool-result"})}),A=!0}else t.state==="approval-pending"?(q(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:ue(t.approval)}),A=!0,r&&P({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:r.agentSessionId,attentionId:X("approval",t.runId,t.seq)})):(t.state==="error"||t.state==="aborted")&&t.message&&(q(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:t.message}),A=!0);A&&E(e.client);const k=`${n}:${t.runId}`;if(t.state==="delta"&&!t.thinking&&t.text&&e.runTextAcc.set(k,(e.runTextAcc.get(k)??"")+t.text),t.agentSessionId&&t.agentSessionId!==i&&(i=t.agentSessionId,e.nativeFusion?.noteManagedSourceSession(n,Z(s,t.source),t.agentSessionId),r&&P({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:t.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n,agentType:s,agentSessionId:t.agentSessionId}}})),t.state==="delta"){const m=t.text??"";if(!m)return;r?.pendingTextEvent&&r.pendingTextEvent.runId!==t.runId&&l(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:t.runId,seq:t.seq,text:"",thinking:!1}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=m,r.pendingTextEvent.seq=t.seq);return}let $={};if(t.state==="final"){l(r);const m=e.runTextAcc.get(k)??"";m&&($={messageSummary:Ee(m)}),e.runTextAcc.delete(k)}else t.state==="error"||t.state==="aborted"?(l(r),e.runTextAcc.delete(k)):(t.state==="tool-call"||t.state==="tool-result"||t.state==="approval-pending")&&l(r);R&&r?.currentRunId===t.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,I(r),e.chatQueue?.noteTerminal(n),P({sessionId:n,agentType:s,workDir:r.workDir,agentSessionId:r.agentSessionId,status:t.state==="final"?"completed":"failed",attentionId:X("terminal",t.runId),lastActivityAt:new Date().toISOString()}),E(e.client)),c(t,$)}),a.on("error",t=>{console.error(`[chat.send] adapter error sessionId=${n} agentType=${s}: ${t.message}`),e.sessions.delete(n),e.chatQueue?.noteTerminal(n),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:n,message:t.message,runId:"",seq:0}})})}function qe(e,n){if(e.processedReqIds.add(n),e.processedReqIds.size>1e3){const s=e.processedReqIds.values().next().value;e.processedReqIds.delete(s)}}async function v(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function ae(e,n,s,a,i,c,I){return I?e.send(n,s,a,i,c,I):c?.trim()?e.send(n,s,a,i,c):i?.length?e.send(n,s,a,i):a?e.send(n,s,a):e.send(n,s)}async function De(e,n,s,a,i,c,I,S){e.evictIdleSessions();const x=ce(s);if(!x)throw new Error(`Unsupported agent: ${s}`);const l=a==="manager"&&s!=="manager"?new fe(x,s==="claude"?"claude":"codex"):x;ne(l,n,s,I,S),await l.start(n,i,c);const t={adapter:l,workDir:i,agentType:s,sessionMode:a,agentSessionId:c??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:I??null,externalReplyTarget:S??null,externalChannelEnv:{...ee(s),...te(n,I,S)}};return e.sessions.set(n,t),be(e,n,s,l),t}function $e(e,n){const s=e.sessions.get(n),a=s?.currentRunId;if(!s||!a)return;const i=s.nextEventSeq;e.runTextAcc.delete(`${n}:${a}`),s.pendingTextEvent=null,s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:a,seq:i},seq:i,id:`agent-evt-${a}-${i}`}),e.chatQueue?.noteTerminal(n)}async function je(e,n,s){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}qe(e,n.id);const{sessionId:a,text:i,agentType:c,sessionMode:I,managerConfig:S,workDir:x,agentSessionId:l,modelId:t,systemPrompt:r,managerDefaultWorkerAgentType:T,managerDefaultWorkerModelId:R,reasoningEffort:A,clientMessageId:k,sessionListProjection:$,waitForDispatch:m,responseId:se,managedRoomContextToken:C,suppressExternalOwnerEcho:re}=n.params,h=se||n.id;Ie($);const W=null,H="";if(!a||!i){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:"sessionId and text are required"});return}const g=c,D=g==="manager"||I==="manager"?"manager":"standard";D==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(a,T??null,R??null);const O=(g==="claude"||g==="codex"||g==="workbuddy")&&typeof A=="string"&&A.trim()?A.trim():void 0,M=e.resolvePath(Te(g,x||j.homedir(),l)||j.homedir()),oe=M,_=ye(n.params.attachments);_?.length&&Se(M,_);const y={text:i,attachments:_,localized:!1};let d=e.sessions.get(a);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==g||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==D||d.workDir!==M||JSON.stringify(d.externalChannel??null)!==JSON.stringify(W??null)){e.sessions.delete(a);try{await v(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}else if(l&&d.agentSessionId!==l)try{await d.adapter.resume(l),d.agentSessionId=l}catch{e.sessions.delete(a);try{await v(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}}if(!d)try{d=await De(e,a,g,D,M,l,W,H)}catch(o){const f=o instanceof Error&&o.message.startsWith("Unsupported agent:")?o.message:`Failed to start ${c}: ${o instanceof Error?o.message:String(o)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${a} agentType=${c} workDir=${M} agentSessionId=${l??""}: ${f}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:f,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:f});return}C&&/^mrc_[A-Za-z0-9_-]{43}$/.test(C)&&ne(d.adapter,a,g,W,H,C);const p={id:k??`user-${n.id}`,sessionId:a,role:"user",ts:Date.now(),payload:ge(y.text,y.attachments)},w=me(a,p.id),L=w.state==="queued";if(w.state!=="not_found"&&!L){e.client.sendRes({type:"res",id:h,ok:!0,payload:w});return}b({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:g,sessionMode:D,managerConfig:S??null,modelId:t,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:R??null,reasoningEffort:O}});const F=(o,f)=>({...G(K()),sessionId:a,clientMessageId:p.id,messageId:p.id,deliveryState:o,bodyRevision:f,committedAt:new Date(p.ts).toISOString()}),U=o=>{e.nativeFusion?.registerManagedSend({sessionId:a,agentType:g,sourceAgentType:Z(g,t),canonicalMessageId:p.id,sourceSessionKey:d.agentSessionId??l??null,text:y.text,managedEchoPolicy:o?.deliveryMode==="external_owner_queue"&&!re?"import_following":"suppress_following",externalRunId:o?.deliveryMode==="external_owner_queue"?`codex-external-${o.queuedSubmissionId??p.id}`:null})},B=()=>{if(U(),L)return{...G(K()),sessionId:a,clientMessageId:w.clientMessageId,messageId:w.messageId,deliveryState:w.deliveryState,bodyRevision:w.bodyRevision,committedAt:w.committedAt};try{Me(e,{sessionId:a,agentType:g,sessionMode:D,managerConfig:S??null,workDir:oe,agentSessionId:d.agentSessionId??l??null,modelId:t,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:R??null});const o=q(a,p,"committed");return Ae(M,_??[]),E(e.client),F("committed",o)}catch(o){throw e.nativeFusion?.cancelManagedSend?.(p.id),o}},Q=o=>{d.currentRunId||(d.nextEventSeq=0),U(o);const f=o?.deliveryMode==="external_owner_queue"?"queued":"executing",u=z(a,p,f);return E(e.client),F(f,u)},J=async(o,f)=>{e.nativeFusion?.cancelManagedSend?.(p.id);const u=Y(g,o);console.error(`[chat.send] send failed reqId=${n.id} sessionId=${a} agentType=${c} workDir=${M} agentSessionId=${d.agentSessionId??l??""}: ${u}`),e.sessions.delete(a);try{await v(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:u,runId:"",seq:0}}),!f){const N={id:`agent-error-${n.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:u};q(a,N),E(e.client)}f&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:u}))};if(m){try{B()}catch(u){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${u instanceof Error?u.message:String(u)}`});return}let o;try{const u=y.attachments;o=await ae(d.adapter,y.text,t,O,u,r,s),b({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(u){const N=z(a,p,"failed");E(e.client);const ie=Y(g,u);await J(u,!1),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...F("failed",N),failureMessage:ie}});return}const f=Q(o??void 0);e.client.sendRes({type:"res",id:h,ok:!0,payload:{...f,...y.localized?{localizedAttachments:!0}:{},...o?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:o.deliveryMode,queuedSubmissionId:o.queuedSubmissionId}:{}}}),b({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let V;try{V=B()}catch(o){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${o instanceof Error?o.message:String(o)}`});return}Q(),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...V,...y.localized?{localizedAttachments:!0}:{}}}),b({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const de=y.attachments;ae(d.adapter,y.text,t,O,de,r,s).then(()=>{b({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(o=>{z(a,p,"failed"),E(e.client),J(o,!1)})}async function Ke(e,n){const{sessionId:s}=n.params,a=e.sessions.get(s);if(a){try{await a.adapter.stop()}catch{}$e(e,s),e.activityPublisher?.publish(s,null)}e.client.sendRes({type:"res",id:n.id,ok:!0})}export{Ke as handleChatAbort,je as handleChatSend,Me as sendSessionUpdateEvent};
1
+ import ee from"node:os";import{createHash as he}from"node:crypto";import{createAgent as ye}from"../../agents/adapter.js";import{buildApprovalPendingPayload as Se,buildUserMessagePayload as te}from"@shennian/wire";import{ManagerModeAdapter as Ae}from"../../agents/manager.js";import{reportLog as q}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as xe}from"../../native-fusion/parsers.js";import{appendMessage as D,getDaemonInstallationId as ne,getMessageDeliveryStatus as Re,recordSession as C,upsertMessage as v}from"../store.js";import{mergeProjectedSessions as Ee}from"../projection.js";import{buildManagedAgentEnv as we}from"../../agents/config-status.js";import{parsePersonalAttachmentReferences as ke}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as Me,markAttachmentReferences as Te}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as E}from"../index-publisher.js";import{writeToolDetail as be}from"../tool-detail-store.js";import{getPersonalSyncIdentity as ae}from"../../personal-sync/epoch.js";function qe(e){const n=e.indexOf(`
2
+ `),r=n>0?Math.min(n,80):Math.min(e.length,80);return e.slice(0,r)}function re(e,n,r){return`${e}:${he("sha256").update(`${n}\0${r??""}`).digest("hex")}`}function De(e,n,r={}){if(e.state==="tool-call"||e.state==="tool-result"){const a={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:n,detailRef:a,status:e.state==="tool-call"?"running":"completed",detailAvailable:!0,...e.durationMs!==void 0?{durationMs:e.durationMs}:{},...e.name?{name:e.name}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...r}}return{...e,sessionId:n,...r}}const $e=3e4;function _e(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function se(e,n){return`Agent send failed: ${n instanceof Error?n.message:String(n)}`}function oe(e,n){return e!=="manager"?e:n==="claude"?"claude":"codex"}function Pe(e,n){C({sessionId:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:n.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n.sessionId,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,workDir:n.workDir,status:"active",externalChannel:null}}})}function de(e){return we(e)}function ie(e,n,r){return{}}function le(e,n,r,a,i,u){e.configure?.({sessionId:n,externalChannel:a??null,env:{...de(r),...ie(n,a,i),...u?{SHENNIAN_MANAGED_ROOM_CONTEXT:u}:{}}})}function Ce(e,n,r){if(e!=="claude"||!r)return n;const a=n.trim();return!!a&&a.startsWith("-")&&!a.includes("/")?xe(r)??n:n}function ve(e,n,r,a){let i=null;function u(t,s={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:De(t,n,s),seq:t.seq,id:`agent-evt-${t.runId}-${t.seq}`})}function m(t){t?.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null)}function S(t){if(!t.currentRunId||!t.currentRunPhase)return;const s=t.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:n,runId:t.currentRunId,seq:s,runPhase:t.currentRunPhase},seq:s,id:`agent-heartbeat-${t.currentRunId}-${s}-${Date.now()}`})}function w(t){!t||t.heartbeatTimer||(t.heartbeatTimer=setInterval(()=>{S(t)},$e),t.heartbeatTimer.unref?.())}function l(t){const s=t?.pendingTextEvent;!t||!s||!s.text||(D(n,{id:`agent-${s.runId}-${s.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:s.text}),E(e.client),u({state:"delta",runId:s.runId,seq:s.seq,text:s.text,thinking:s.thinking||void 0}),t.pendingTextEvent=null)}a.on("agentEvent",t=>{const s=e.sessions.get(n),b=_e(t),k=t.state==="final"||t.state==="error"||t.state==="aborted";if(s&&(s.nextEventSeq=t.seq+1,k||(s.currentRunId=t.runId),!k&&b&&(s.currentRunPhase=b,w(s)),t.agentSessionId&&(s.agentSessionId=t.agentSessionId)),e.managerRuntime?.noteAgentEvent(n,t),t.state==="delta"&&t.thinking)return;t.state!=="delta"&&q({level:"info",sessionId:n,wsEvent:`agent.${t.state}`,wsDirection:"out",metadata:{runId:t.runId,seq:t.seq,agentType:r}});let A=!1;if(t.state==="tool-call"||t.state==="tool-result"){const p=`agent-${t.runId}-${t.seq}`;be({sessionId:n,toolCallId:p,detailRevision:t.seq,ts:Date.now(),name:t.name??"tool",...t.args!==void 0?{args:t.args}:{},...t.result!==void 0?{result:t.result}:{},status:t.state==="tool-call"?"running":"completed",...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq}}),D(n,{id:p,sessionId:n,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:t.state==="tool-call"?"tool_use":"tool_result",name:t.name,toolCallId:p,status:t.state==="tool-call"?"running":"completed",...t.durationMs!==void 0?{durationMs:t.durationMs}:{},detailRef:{runId:t.runId,sourceSeq:t.seq},detailRevision:t.seq,detailAvailable:!0,resultOmitted:t.state==="tool-result"})}),A=!0}else t.state==="approval-pending"?(D(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:Se(t.approval)}),A=!0,s&&C({sessionId:n,agentType:r,workDir:s.workDir,agentSessionId:s.agentSessionId,attentionId:re("approval",t.runId,t.seq)})):(t.state==="error"||t.state==="aborted")&&t.message&&(D(n,{id:`agent-${t.runId||"run"}-${t.seq}`,sessionId:n,role:"agent",ts:Date.now(),payload:t.message}),A=!0);A&&E(e.client);const M=`${n}:${t.runId}`;if(t.state==="delta"&&!t.thinking&&t.text&&e.runTextAcc.set(M,(e.runTextAcc.get(M)??"")+t.text),t.agentSessionId&&t.agentSessionId!==i&&(i=t.agentSessionId,e.nativeFusion?.noteManagedSourceSession(n,oe(r,t.source),t.agentSessionId),s&&C({sessionId:n,agentType:r,workDir:s.workDir,agentSessionId:t.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:n,agentType:r,agentSessionId:t.agentSessionId}}})),t.state==="delta"){const p=t.text??"";if(!p)return;s?.pendingTextEvent&&s.pendingTextEvent.runId!==t.runId&&l(s),s&&!s.pendingTextEvent&&(s.pendingTextEvent={runId:t.runId,seq:t.seq,text:"",thinking:!1}),s?.pendingTextEvent&&(s.pendingTextEvent.text+=p,s.pendingTextEvent.seq=t.seq);return}let _={};if(t.state==="final"){l(s);const p=e.runTextAcc.get(M)??"";p&&(_={messageSummary:qe(p)}),e.runTextAcc.delete(M)}else t.state==="error"||t.state==="aborted"?(l(s),e.runTextAcc.delete(M)):(t.state==="tool-call"||t.state==="tool-result"||t.state==="approval-pending")&&l(s);k&&s?.currentRunId===t.runId&&(s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,m(s),e.chatQueue?.noteTerminal(n),C({sessionId:n,agentType:r,workDir:s.workDir,agentSessionId:s.agentSessionId,status:t.state==="final"?"completed":"failed",attentionId:re("terminal",t.runId),lastActivityAt:new Date().toISOString()}),E(e.client)),u(t,_)}),a.on("error",t=>{console.error(`[chat.send] adapter error sessionId=${n} agentType=${r}: ${t.message}`),e.sessions.delete(n),e.chatQueue?.noteTerminal(n),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:n,message:t.message,runId:"",seq:0}})})}function We(e,n){if(e.processedReqIds.add(n),e.processedReqIds.size>1e3){const r=e.processedReqIds.values().next().value;e.processedReqIds.delete(r)}}async function U(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function ce(e,n,r,a,i,u,m){return m?e.send(n,r,a,i,u,m):u?.trim()?e.send(n,r,a,i,u):i?.length?e.send(n,r,a,i):a?e.send(n,r,a):e.send(n,r)}async function Oe(e,n,r,a,i,u,m,S){e.evictIdleSessions();const w=ye(r);if(!w)throw new Error(`Unsupported agent: ${r}`);const l=a==="manager"&&r!=="manager"?new Ae(w,r==="claude"?"claude":"codex"):w;le(l,n,r,m,S),await l.start(n,i,u);const t={adapter:l,workDir:i,agentType:r,sessionMode:a,agentSessionId:u??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:m??null,externalReplyTarget:S??null,externalChannelEnv:{...de(r),...ie(n,m,S)}};return e.sessions.set(n,t),ve(e,n,r,l),t}function Fe(e,n){const r=e.sessions.get(n),a=r?.currentRunId;if(!r||!a)return;const i=r.nextEventSeq;e.runTextAcc.delete(`${n}:${a}`),r.pendingTextEvent=null,r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,r.heartbeatTimer&&(clearInterval(r.heartbeatTimer),r.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:n,runId:a,seq:i},seq:i,id:`agent-evt-${a}-${i}`}),e.chatQueue?.noteTerminal(n)}async function tt(e,n,r){if(e.processedReqIds.has(n.id)){e.client.sendRes({type:"res",id:n.id,ok:!0});return}We(e,n.id);const{sessionId:a,text:i,agentType:u,sessionMode:m,managerConfig:S,workDir:w,agentSessionId:l,modelId:t,systemPrompt:s,managerDefaultWorkerAgentType:b,managerDefaultWorkerModelId:k,reasoningEffort:A,clientMessageId:M,sessionListProjection:_,waitForDispatch:p,responseId:ue,managedRoomContextToken:W,suppressExternalOwnerEcho:ge,queuedBatchItems:O}=n.params,h=ue||n.id;Ee(_);const F=null,Q="";if(!a||!i){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:"sessionId and text are required"});return}const f=u,$=f==="manager"||m==="manager"?"manager":"standard";$==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(a,b??null,k??null);const N=(f==="claude"||f==="codex"||f==="workbuddy")&&typeof A=="string"&&A.trim()?A.trim():void 0,T=e.resolvePath(Ce(f,w||ee.homedir(),l)||ee.homedir()),fe=T,P=ke(n.params.attachments);P?.length&&Me(T,P);const y={text:i,attachments:P,localized:!1};let d=e.sessions.get(a);if(d){if(d.lastActiveAt=Date.now(),d.agentType!==f||(d.sessionMode??(d.agentType==="manager"?"manager":"standard"))!==$||d.workDir!==T||JSON.stringify(d.externalChannel??null)!==JSON.stringify(F??null)){e.sessions.delete(a);try{await U(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}else if(l&&d.agentSessionId!==l)try{await d.adapter.resume(l),d.agentSessionId=l}catch{e.sessions.delete(a);try{await U(d)}catch{e.processedReqIds.delete(n.id)}d=void 0}}if(!d)try{d=await Oe(e,a,f,$,T,l,F,Q)}catch(o){const g=o instanceof Error&&o.message.startsWith("Unsupported agent:")?o.message:`Failed to start ${u}: ${o instanceof Error?o.message:String(o)}`;console.error(`[chat.send] start failed reqId=${n.id} sessionId=${a} agentType=${u} workDir=${T} agentSessionId=${l??""}: ${g}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:g,runId:"",seq:0}}),e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:g});return}W&&/^mrc_[A-Za-z0-9_-]{43}$/.test(W)&&le(d.adapter,a,f,F,Q,W);const I={id:M??`user-${n.id}`,sessionId:a,role:"user",ts:Date.now(),payload:te(y.text,y.attachments)},J=Array.isArray(O)&&O.length>0?O.map(o=>({id:o.clientMessageId??o.id,sessionId:a,role:"user",ts:Date.parse(o.createdAt),payload:te(o.text,o.attachments)})):null,z=J??[I],x=Re(a,I.id),V=x.state==="queued";if(x.state!=="not_found"&&!V){e.client.sendRes({type:"res",id:h,ok:!0,payload:x});return}q({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:n.id,agentType:f,sessionMode:$,managerConfig:S??null,modelId:t,managerDefaultWorkerAgentType:b??null,managerDefaultWorkerModelId:k??null,reasoningEffort:N}});const B=(o,g)=>({...ae(ne()),sessionId:a,clientMessageId:I.id,messageId:I.id,deliveryState:o,bodyRevision:g,committedAt:new Date(I.ts).toISOString()}),j=o=>{e.nativeFusion?.registerManagedSend({sessionId:a,agentType:f,sourceAgentType:oe(f,t),canonicalMessageId:I.id,sourceSessionKey:d.agentSessionId??l??null,text:y.text,managedEchoPolicy:o?.deliveryMode==="external_owner_queue"&&!ge?"import_following":"suppress_following",externalRunId:o?.deliveryMode==="external_owner_queue"?`codex-external-${o.queuedSubmissionId??I.id}`:null})},K=()=>{d.currentRunId||(d.currentRunId=`dispatch:${n.id}`,d.currentRunPhase="thinking",d.nextEventSeq=0)},G=()=>{if(j(),V)return{...ae(ne()),sessionId:a,clientMessageId:x.clientMessageId,messageId:x.messageId,deliveryState:x.deliveryState,bodyRevision:x.bodyRevision,committedAt:x.committedAt};try{Pe(e,{sessionId:a,agentType:f,sessionMode:$,managerConfig:S??null,workDir:fe,agentSessionId:d.agentSessionId??l??null,modelId:t,managerDefaultWorkerAgentType:b??null,managerDefaultWorkerModelId:k??null});let o=0;for(const[g,c]of z.entries()){const R=J?v(a,c,"committed"):D(a,c,"committed");g===0&&(o=R)}return Te(T,P??[]),E(e.client),B("committed",o)}catch(o){throw e.nativeFusion?.cancelManagedSend?.(I.id),o}},X=o=>{d.currentRunId||(d.nextEventSeq=0),j(o);const g=o?.deliveryMode==="external_owner_queue"?"queued":"executing";let c=0;for(const[R,H]of z.entries()){const L=v(a,H,g);R===0&&(c=L)}return E(e.client),B(g,c)},Y=async(o,g)=>{e.nativeFusion?.cancelManagedSend?.(I.id);const c=se(f,o);console.error(`[chat.send] send failed reqId=${n.id} sessionId=${a} agentType=${u} workDir=${T} agentSessionId=${d.agentSessionId??l??""}: ${c}`),e.sessions.delete(a);try{await U(d)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:c,runId:"",seq:0}}),!g){const R={id:`agent-error-${n.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:c};D(a,R),E(e.client)}g&&(e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:c}))};if(p){try{G()}catch(c){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${c instanceof Error?c.message:String(c)}`});return}K();let o;try{const c=y.attachments;o=await ce(d.adapter,y.text,t,N,c,s,r),q({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}catch(c){let R=0;for(const[L,me]of z.entries()){const Ie=v(a,me,"failed");L===0&&(R=Ie)}E(e.client);const H=se(f,c);await Y(c,!1),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...B("failed",R),failureMessage:H}});return}const g=X(o??void 0);e.client.sendRes({type:"res",id:h,ok:!0,payload:{...g,...y.localized?{localizedAttachments:!0}:{},...o?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:o.deliveryMode,queuedSubmissionId:o.queuedSubmissionId}:{}}}),q({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});return}let Z;try{Z=G()}catch(o){e.processedReqIds.delete(n.id),e.client.sendRes({type:"res",id:h,ok:!1,error:`Durable message commit failed: ${o instanceof Error?o.message:String(o)}`});return}K(),X(),e.client.sendRes({type:"res",id:h,ok:!0,payload:{...Z,...y.localized?{localizedAttachments:!0}:{}}}),q({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:n.id,ok:!0}});const pe=y.attachments;ce(d.adapter,y.text,t,N,pe,s,r).then(()=>{q({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:n.id}})}).catch(o=>{v(a,I,"failed"),E(e.client),Y(o,!1)})}async function nt(e,n){const{sessionId:r}=n.params,a=e.sessions.get(r);if(a){try{await a.adapter.stop()}catch{}Fe(e,r),e.activityPublisher?.publish(r,null)}e.client.sendRes({type:"res",id:n.id,ok:!0})}export{nt as handleChatAbort,tt as handleChatSend,Pe as sendSessionUpdateEvent};
@@ -1,8 +1,9 @@
1
- import { type AgentType, type ReqFrame } from '@shennian/wire';
1
+ import { type ReqFrame } from '@shennian/wire';
2
2
  import type { CliRelayClient } from '../relay/client.js';
3
3
  import type { UpgradePolicyController } from './types.js';
4
4
  import type { UpgradeIdleState } from '../upgrade/scheduler.js';
5
5
  import type { NativeSessionFusionService } from '../native-fusion/service.js';
6
+ import { type ManagedRoomEnqueueParams, type ManagedRoomQueueContext, type PreparedManagedRoomTurn } from './queue.js';
6
7
  import '../agents/claude.js';
7
8
  import '../agents/codex.js';
8
9
  import '../agents/workbuddy.js';
@@ -35,25 +36,15 @@ export declare class SessionManager {
35
36
  constructor(client: CliRelayClient, nativeFusion?: NativeSessionFusionService | null, cliVersion?: string | undefined, upgradePolicyController?: UpgradePolicyController | null, managedRoomBinder?: ManagedRoomBinder | null);
36
37
  private getRuntime;
37
38
  getUpgradeIdleState(): UpgradeIdleState;
39
+ setManagedRoomTurnPreparer(prepare: (contexts: ManagedRoomQueueContext[]) => Promise<PreparedManagedRoomTurn>): void;
40
+ enqueueManagedRoomBatch(inputs: Array<{
41
+ params: ManagedRoomEnqueueParams;
42
+ context: ManagedRoomQueueContext;
43
+ }>): Promise<void>;
44
+ private dispatchQueuedChat;
38
45
  synchronizeSessionDeletions(): Promise<void>;
39
46
  private synchronizeSessionDeletionsOnce;
40
47
  private applySessionDeletion;
41
- activateRoomSession(input: {
42
- sessionId: string;
43
- agentType: AgentType;
44
- agentSessionId: string;
45
- workDir: string;
46
- text: string;
47
- clientMessageId: string;
48
- systemPrompt: string;
49
- roomContextRegistryKey: string;
50
- managedRoomContextToken?: string;
51
- }): Promise<{
52
- externalOwnerQueued: boolean;
53
- }>;
54
- abortRoomSession(input: {
55
- sessionId: string;
56
- }): Promise<void>;
57
48
  private publishSessionActivity;
58
49
  private handleExternalTerminal;
59
50
  private publishManagedActivitySnapshots;
@@ -1 +1 @@
1
- import{SESSION_SYNC_PROTOCOL_VERSION as u,SESSION_SYNC_DATA_EPOCH as m,isAvailableAgentType as S}from"@shennian/wire";import h from"node:crypto";import{getRegisteredAgents as R,unregisterAgent as k}from"../agents/adapter.js";import{loadConfig as I}from"../config/index.js";import{handleUpgradeStart as A,handleUpgradeStatus as T}from"../commands/upgrade.js";import{handleAgentsRefresh as C,handleModelsRefresh as P}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as E,handleAgentConfigGet as x,handleAgentConfigTest as O,handleAgentConfigUpsert as F}from"./handlers/agent-config.js";import{handleChatAbort as p,handleChatSend as f}from"./handlers/chat.js";import{handleSessionRefresh as M}from"./handlers/session-refresh.js";import{handleSessionToolDetail as _}from"./handlers/tool-detail.js";import{handleSessionTitleSet as N}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as Q}from"./handlers/voice-window.js";import{cleanupPendingTransfers as z,handleFsLs as V,handleFsRead as W,handleFsRename as $,handleFsWrite as j,handleFsTransferAbort as B,handleFsTransferChunk as L,handleFsTransferFinish as H,handleFsTransferStart as Y,handleFsExportMarkdownPdf as G,handleFsExportMarkdownPdfSetup as K,handleFsArchiveZip as X}from"./handlers/fs.js";import{handleRegionProbe as Z,handleRegionSwitch as J,handleUpgradeSetPolicy as q}from"./handlers/control.js";import{ManagerRuntimeService as ee,setManagerRuntimeService as g}from"../manager/runtime.js";import{ChatQueueManager as se}from"./queue.js";import{createAuthorizedFsRoot as te,resolveAuthorizedPath as ie,resolveSessionWorkDir as ae}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as ne}from"../agents/custom.js";import{handleManagedRoomBind as re,handleManagedRoomRestart as oe}from"./handlers/room-managed.js";import{getDaemonInstallationId as v,listSessionRecords as b,purgeCanonicalSession as ce,purgeSessionRecord as le}from"./store.js";import{PersonalHistorySubscriptions as he}from"./history-subscriptions.js";import{sweepAttachmentTransfers as de}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ue}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as me}from"./tool-detail-store.js";import{readDeletionSyncRevision as pe,writeDeletionSyncRevision as y}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as fe,clearDeletionSyncFailure as ge,DeletionSyncRetryWaitError as ve,DeletionSyncTerminalError as be,recordDeletionSyncFailure as d}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as ye}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as c}from"../personal-sync/epoch.js";const we=50;function Se(o,e){if(!o||!Number.isSafeInteger(o.baselineRevision)||o.baselineRevision<e||!Number.isSafeInteger(o.latestRevision)||o.latestRevision<o.baselineRevision||!Array.isArray(o.deletions))throw new Error("invalid deletion sync response");let s=o.baselineRevision;for(const t of o.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const i of t.sources)if(!i?.sourceSessionKey||!S(i.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==o.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as cs}from"../fs/boundary.js";class ns{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,s=null,t,i=null,a=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=i,this.managedRoomBinder=a,this.managerRuntime=new ee({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.chatQueue=new se({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.historySubscriptions=new he(this.client),this.nativeCleanupOutbox=new ye({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(n=>{this.handleExternalTerminal(n)}),this.nativeCleanupOutbox.start(),g(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...b().map(r=>r.workDir),...Array.from(this.pendingTransfers.values()).map(r=>r.rootPath)]);for(const r of n)try{de(this.resolvePath(r))}catch(l){console.error("[attachment.cleanup] failed",l instanceof Error?l.message:String(l))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,s)=>this.resolveAuthorizedPath(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}async synchronizeSessionDeletions(){fe();try{await this.synchronizeSessionDeletionsOnce(),ge()}catch(e){if(e instanceof ve||e instanceof be)throw e;const s=e instanceof Error?e.message:String(e),t=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(s);throw d({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=v(),s=pe(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${h.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const r=t.rejection;throw r?(d({code:r.code,retryable:r.retryable&&r.requiredAction==="retry"}),new Error(r.code)):(d({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const i=t.payload;c(i),Se(i,s);let a=i.baselineRevision;a>s&&y(a);for(const r of i.deletions)r.deletionRevision<=a||(await this.applySessionDeletion(r),a=r.deletionRevision,y(a));if(a!==i.latestRevision)throw new Error(`deletion revision gap: applied ${a}, latest ${i.latestRevision}`);if(a===0)return;const n=await this.client.sendReq({type:"req",id:`session-deletions-ack-${h.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:u,dataEpoch:m,daemonInstallationId:e,deletionRevision:a}});if(!n.ok)throw new Error(n.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new ue({daemonInstallationId:v()});s.recordSession(e);for(const i of e.sources)s.record({...e,...i});const t=this.sessions.get(e.sessionId);if(t){try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),me(e.sessionId),ce(e.sessionId),le(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}async activateRoomSession(e){const s=b().find(n=>n.sessionId===e.sessionId),t=s?.agentType===e.agentType?s.modelId??void 0:void 0,i={value:null},a=new Proxy(this.client,{get:(n,r)=>{if(r==="sendRes")return w=>{i.value=w};const l=Reflect.get(n,r,n);return typeof l=="function"?l.bind(n):l}});return await f({...this.getRuntime(),client:a},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,modelId:t,sessionMode:s?.sessionMode??(s?.agentType==="manager"?"manager":"standard"),managerConfig:s?.managerConfig??null,managerDefaultWorkerAgentType:s?.managerDefaultWorkerAgentType??null,managerDefaultWorkerModelId:s?.managerDefaultWorkerModelId??null,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:i.value?.ok===!0&&i.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const s=new Proxy(this.client,{get:(t,i)=>{if(i==="sendRes")return()=>{};const a=Reflect.get(t,i,t);return typeof a=="function"?a.bind(t):a}});await p({...this.getRuntime(),client:s},{type:"req",id:`room-activation-abort-${h.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,s){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:s}})}async handleExternalTerminal(e){const s=this.sessions.get(e);s&&(this.sessions.delete(e),s.heartbeatTimer&&clearInterval(s.heartbeatTimer),s.adapter.removeAllListeners(),await s.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const i=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:i,canStop:t.canStop??!0});const a=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:a,runPhase:t.runPhase,canStop:t.canStop??!0},seq:a,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of R())s.startsWith("custom:")&&k(s);const e=I();for(const[s,t]of Object.entries(e.customAgents??{}))ne(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":c(e.params),await f(s,e);break;case"chat.enqueue":c(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":c(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":c(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":c(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":c(e.params),await p(s,e);break;case"session.refresh":c(e.params),await M(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":U(s,e);break;case"session.tool.detail":await _(s,e);break;case"voice.session.body.window":Q(s,e);break;case"session.title.set":await N(s,e);break;case"room.agent.bind-managed":await re(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await oe(s,this.managedRoomBinder,e);break;case"fs.ls":await V(s,e);break;case"fs.read":await W(s,e);break;case"fs.write":await j(s,e);break;case"fs.export.markdown-pdf":await G(s,e);break;case"fs.export.markdown-pdf.setup":await K(s,e);break;case"fs.archive.zip":await X(s,e);break;case"fs.rename":await $(s,e);break;case"fs.transfer.start":await Y(s,e);break;case"fs.transfer.chunk":await L(s,e);break;case"fs.transfer.finish":await H(s,e);break;case"fs.transfer.abort":await B(s,e);break;case"region.probe":await Z(s,e);break;case"region.switch":await J(s,e);break;case"upgrade.start":await A(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await T(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await q(s,e);break;case"agents.refresh":await C(s,e);break;case"models.refresh":await P(s,e);break;case"agent.config.get":await x(s,e);break;case"agent.config.upsert":await F(s,e);break;case"agent.config.clear":await E(s,e);break;case"agent.config.test":await O(s,e);break;case"agent.capabilities.list":await D(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<we)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return ae(e)}resolveAuthorizedPath(e,s){return ie(e,te(s))}async cleanup(){this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),z(this.getRuntime()),await this.managerRuntime.stop(),g(null)}}export{ns as SessionManager,cs as resolveSessionWorkDir};
1
+ import{SESSION_SYNC_PROTOCOL_VERSION as d,SESSION_SYNC_DATA_EPOCH as u,isAvailableAgentType as y}from"@shennian/wire";import m from"node:crypto";import{getRegisteredAgents as w,unregisterAgent as S}from"../agents/adapter.js";import{loadConfig as R}from"../config/index.js";import{handleUpgradeStart as k,handleUpgradeStatus as A}from"../commands/upgrade.js";import{handleAgentsRefresh as I,handleModelsRefresh as T}from"./handlers/agents.js";import{handleAgentCapabilitiesList as C}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as P,handleAgentConfigGet as D,handleAgentConfigTest as E,handleAgentConfigUpsert as M}from"./handlers/agent-config.js";import{handleChatAbort as x,handleChatSend as p}from"./handlers/chat.js";import{handleSessionRefresh as O}from"./handlers/session-refresh.js";import{handleSessionToolDetail as F}from"./handlers/tool-detail.js";import{handleSessionTitleSet as _}from"./handlers/title.js";import{handleSessionMessageStatus as N}from"./handlers/message-status.js";import{handleVoiceSessionBodyWindow as Q}from"./handlers/voice-window.js";import{cleanupPendingTransfers as U,handleFsLs as W,handleFsRead as z,handleFsRename as V,handleFsWrite as B,handleFsTransferAbort as j,handleFsTransferChunk as $,handleFsTransferFinish as L,handleFsTransferStart as H,handleFsExportMarkdownPdf as Y,handleFsExportMarkdownPdfSetup as G,handleFsArchiveZip as K}from"./handlers/fs.js";import{handleRegionProbe as X,handleRegionSwitch as Z,handleUpgradeSetPolicy as J}from"./handlers/control.js";import{ManagerRuntimeService as q,setManagerRuntimeService as f}from"../manager/runtime.js";import{ChatQueueManager as ee}from"./queue.js";import{createAuthorizedFsRoot as se,resolveAuthorizedPath as te,resolveSessionWorkDir as ae}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/manager.js";import{registerCustomAgent as ie}from"../agents/custom.js";import{handleManagedRoomBind as ne,handleManagedRoomRestart as re}from"./handlers/room-managed.js";import{getDaemonInstallationId as g,listSessionRecords as v,purgeCanonicalSession as oe,purgeSessionRecord as ce}from"./store.js";import{PersonalHistorySubscriptions as le}from"./history-subscriptions.js";import{sweepAttachmentTransfers as he}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as de}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as ue}from"./tool-detail-store.js";import{readDeletionSyncRevision as me,writeDeletionSyncRevision as b}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as pe,clearDeletionSyncFailure as fe,DeletionSyncRetryWaitError as ge,DeletionSyncTerminalError as ve,recordDeletionSyncFailure as h}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as be}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as c}from"../personal-sync/epoch.js";const ye=50;function we(o,e){if(!o||!Number.isSafeInteger(o.baselineRevision)||o.baselineRevision<e||!Number.isSafeInteger(o.latestRevision)||o.latestRevision<o.baselineRevision||!Array.isArray(o.deletions))throw new Error("invalid deletion sync response");let s=o.baselineRevision;for(const t of o.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of t.sources)if(!a?.sourceSessionKey||!y(a.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==o.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as os}from"../fs/boundary.js";class is{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,s=null,t,a=null,i=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=a,this.managedRoomBinder=i,this.managerRuntime=new q({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n)}),this.chatQueue=new ee({getRuntime:()=>this.getRuntime(),dispatchReq:n=>this.handleReq(n),dispatchQueuedReq:n=>this.dispatchQueuedChat(n)}),this.historySubscriptions=new le(this.client),this.nativeCleanupOutbox=new be({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(n=>{this.handleExternalTerminal(n)}),this.nativeCleanupOutbox.start(),f(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(n=>{console.error("[session.activity] managed probe failed",n)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const n=new Set([...v().map(r=>r.workDir),...Array.from(this.pendingTransfers.values()).map(r=>r.rootPath)]);for(const r of n)try{he(this.resolvePath(r))}catch(l){console.error("[attachment.cleanup] failed",l instanceof Error?l.message:String(l))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,s)=>this.resolveAuthorizedPath(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}async enqueueManagedRoomBatch(e){const s=e.map(t=>{const a=v().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)}async dispatchQueuedChat(e){const s={value:null},t=new Proxy(this.client,{get:(a,i)=>{if(i==="sendRes")return r=>{s.value=r};const n=Reflect.get(a,i,a);return typeof n=="function"?n.bind(a):n}});if(await p({...this.getRuntime(),client:t},e,e.params.roomActivation===!0?{source:"room_activation"}:void 0),!s.value?.ok||s.value.payload?.deliveryState==="failed")throw new Error(s.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){pe();try{await this.synchronizeSessionDeletionsOnce(),fe()}catch(e){if(e instanceof ge||e instanceof ve)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 h({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=g(),s=me(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${m.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:d,dataEpoch:u,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const r=t.rejection;throw r?(h({code:r.code,retryable:r.retryable&&r.requiredAction==="retry"}),new Error(r.code)):(h({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const a=t.payload;c(a),we(a,s);let i=a.baselineRevision;i>s&&b(i);for(const r of a.deletions)r.deletionRevision<=i||(await this.applySessionDeletion(r),i=r.deletionRevision,b(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const n=await this.client.sendReq({type:"req",id:`session-deletions-ack-${m.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:d,dataEpoch:u,daemonInstallationId:e,deletionRevision:i}});if(!n.ok)throw new Error(n.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new de({daemonInstallationId:g()});s.recordSession(e);for(const a of e.sources)s.record({...e,...a});const t=this.sessions.get(e.sessionId);if(t){try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),ue(e.sessionId),oe(e.sessionId),ce(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),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 w())s.startsWith("custom:")&&S(s);const e=R();for(const[s,t]of Object.entries(e.customAgents??{}))ie(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":c(e.params),await p(s,e,e.params.roomActivation===!0?{source:"room_activation"}:void 0);break;case"chat.enqueue":c(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":c(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":c(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":c(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":c(e.params),await x(s,e);break;case"session.refresh":c(e.params),await O(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":N(s,e);break;case"session.tool.detail":await F(s,e);break;case"voice.session.body.window":Q(s,e);break;case"session.title.set":await _(s,e);break;case"room.agent.bind-managed":await ne(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await re(s,this.managedRoomBinder,e);break;case"fs.ls":await W(s,e);break;case"fs.read":await z(s,e);break;case"fs.write":await B(s,e);break;case"fs.export.markdown-pdf":await Y(s,e);break;case"fs.export.markdown-pdf.setup":await G(s,e);break;case"fs.archive.zip":await K(s,e);break;case"fs.rename":await V(s,e);break;case"fs.transfer.start":await H(s,e);break;case"fs.transfer.chunk":await $(s,e);break;case"fs.transfer.finish":await L(s,e);break;case"fs.transfer.abort":await j(s,e);break;case"region.probe":await X(s,e);break;case"region.switch":await Z(s,e);break;case"upgrade.start":await k(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await A(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await J(s,e);break;case"agents.refresh":await I(s,e);break;case"models.refresh":await T(s,e);break;case"agent.config.get":await D(s,e);break;case"agent.config.upsert":await M(s,e);break;case"agent.config.clear":await P(s,e);break;case"agent.config.test":await E(s,e);break;case"agent.capabilities.list":await C(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<ye)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});e&&(e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return ae(e)}resolveAuthorizedPath(e,s){return te(e,se(s))}async cleanup(){this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),U(this.getRuntime()),await this.managerRuntime.stop(),f(null)}}export{is as SessionManager,os as resolveSessionWorkDir};
@@ -1,14 +1,34 @@
1
- import { type ChatQueueSnapshot, type ReqFrame } from '@shennian/wire';
1
+ import { type ChatEnqueueParams, type ChatQueueSnapshot, type ReqFrame } from '@shennian/wire';
2
2
  import type { LocalReqDispatcher } from '../manager/runtime.js';
3
3
  import type { SessionManagerRuntime } from './types.js';
4
+ export type ManagedRoomQueueContext = {
5
+ registryKey: string;
6
+ causeMessageId: string;
7
+ rootCauseMessageId: string;
8
+ sequence: string;
9
+ hopCount: number;
10
+ };
11
+ export type PreparedManagedRoomTurn = {
12
+ systemPrompt: string;
13
+ roomContextRegistryKey: string;
14
+ managedRoomContextToken: string;
15
+ };
16
+ export type ManagedRoomEnqueueParams = Omit<ChatEnqueueParams, 'protocolVersion' | 'dataEpoch' | 'daemonInstallationId'>;
4
17
  export declare class ChatQueueManager {
5
18
  private opts;
6
19
  private draining;
20
+ private prepareManagedRoomTurn;
7
21
  constructor(opts: {
8
22
  getRuntime: () => SessionManagerRuntime;
9
23
  dispatchReq: LocalReqDispatcher;
24
+ dispatchQueuedReq: LocalReqDispatcher;
10
25
  });
11
26
  getSnapshot(sessionId: string): ChatQueueSnapshot;
27
+ setManagedRoomTurnPreparer(prepare: (contexts: ManagedRoomQueueContext[]) => Promise<PreparedManagedRoomTurn>): void;
28
+ enqueueManagedRoomBatch(inputs: Array<{
29
+ params: ManagedRoomEnqueueParams;
30
+ context: ManagedRoomQueueContext;
31
+ }>): Promise<void>;
12
32
  handleEnqueue(req: ReqFrame): Promise<void>;
13
33
  handleGet(req: ReqFrame): Promise<void>;
14
34
  handleEdit(req: ReqFrame): Promise<void>;
@@ -17,8 +37,7 @@ export declare class ChatQueueManager {
17
37
  purgeSession(sessionId: string): void;
18
38
  private drainIdleQueues;
19
39
  private drainNext;
20
- private mergeExternalMessages;
21
- private dispatchQueuedMessage;
40
+ private dispatchQueuedMessages;
22
41
  private isSessionBusy;
23
42
  private broadcast;
24
43
  }
@@ -1,5 +1,12 @@
1
- import l from"node:fs";import{randomUUID as y}from"node:crypto";import{buildUserMessagePayload as Q}from"@shennian/wire";import{mergeProjectedSessions as $}from"./projection.js";import{parsePersonalAttachmentReferences as I}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as R,markAttachmentReferences as T}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as b}from"../personal-sync/paths.js";import{getPersonalSyncIdentity as M}from"../personal-sync/epoch.js";import{getDaemonInstallationId as x}from"../personal-sync/identity.js";import{appendMessage as W,getMessageDeliveryStatus as v,recordSession as C,tombstoneMessage as N,upsertMessage as P}from"./store.js";import{requestSessionIndexPublish as S}from"./index-publisher.js";function B(){return{sessions:{}}}function k(){return new Date().toISOString()}function j(s){return`enqueue-send-${s}-${y()}`}function f(){try{const s=JSON.parse(l.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 B();throw s}}function p(s){const e=b();l.mkdirSync(e,{recursive:!0,mode:448});const n=A(),t=`${n}.tmp-${process.pid}-${y()}`;try{l.writeFileSync(t,`${JSON.stringify(s,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"});const i=l.openSync(t,"r");if(typeof i=="number")try{l.fsyncSync(i)}finally{l.closeSync(i)}l.renameSync(t,n)}finally{try{l.unlinkSync(t)}catch{}}}function A(){return b("outbound-queue.json")}function z(s){const e=k();return{...M(x()),id:s.queueMessageId||s.clientMessageId||`queue-${y()}`,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:I(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}function q(s){return{id:s.clientMessageId??s.id,sessionId:s.sessionId,role:"user",ts:Date.parse(s.createdAt),payload:Q(s.text,s.attachments)}}class ne{opts;draining=new Set;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{...M(x()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:f().sessions[e]??[]}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if($(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=I(t.attachments);o?.length&&R(i,o);const r={text:t.text,attachments:o,localized:!1},a=await this.isSessionBusy(t.sessionId),d=f(),h=d.sessions[t.sessionId]??[];if(!a&&!h.length){await this.opts.dispatchReq({...e,id:j(e.id),method:"chat.send",params:{...t,text:r.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:r.attachments}});return}const c=z({...t,text:r.text,attachments:r.attachments}),E=c.clientMessageId??c.id,m=v(t.sessionId,E);if(m.state!=="not_found"){n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:m.state==="queued",queueMessageId:c.id,...m,queue:this.getSnapshot(t.sessionId)}});return}const g=U(h,c);g||(d.sessions[t.sessionId]=[...h,c],p(d));const u=g??c;C({sessionId:u.sessionId,agentType:u.agentType,sessionMode:u.sessionMode,managerConfig:u.managerConfig,workDir:u.workDir,agentSessionId:u.agentSessionId,modelId:u.modelId}),W(u.sessionId,q(u),"queued"),T(n.resolvePath(u.workDir),u.attachments??[]),S(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:g?.id??c.id,...v(t.sessionId,g?.clientMessageId??g?.id??c.clientMessageId??c.id),queue:this.getSnapshot(t.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),a||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]??[],r=o.findIndex(h=>h.id===t.queueMessageId);if(r<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}const a=I(t.attachments),d=n.resolvePath(o[r].workDir);a?.length&&R(d,a),o[r]={...o[r],text:t.text,attachments:a,updatedAt:k()},i.sessions[t.sessionId]=o,p(i),P(t.sessionId,q(o[r]),"queued"),o[r].attachments?.length&&T(d,o[r].attachments??[]),S(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]??[],r=o.find(d=>d.id===t.queueMessageId),a=o.filter(d=>d.id!==t.queueMessageId);if(a.length===o.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?i.sessions[t.sessionId]=a:delete i.sessions[t.sessionId],p(i),N(t.sessionId,r?.clientMessageId??r?.id??t.queueMessageId),S(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){F(e),this.draining.delete(e)}drainIdleQueues(){const e=f();for(const n of Object.keys(e.sessions))this.drainNext(n)}async drainNext(e){if(this.draining.has(e)||await this.isSessionBusy(e))return;const i=(f().sessions[e]??[])[0];if(!i){this.broadcast(e);return}const o=i;this.draining.add(e);try{P(e,q(o),"queued"),await this.dispatchQueuedMessage(o);const r=f(),a=(r.sessions[e]??[]).filter(d=>d.id!==i.id);a.length?r.sessions[e]=a:delete r.sessions[e],p(r),this.broadcast(e)}finally{this.draining.delete(e)}}mergeExternalMessages(e,n){const t=[e];for(;n[0]?.origin==="external";)t.push(n.shift());const i=O(t);return i.length===1?i[0]:{...e,id:`external-batch-${e.id}`,text:i.map(o=>o.text).filter(Boolean).join(`
1
+ import p from"node:fs";import{randomUUID as x}from"node:crypto";import{buildUserMessagePayload as j}from"@shennian/wire";import{mergeProjectedSessions as z}from"./projection.js";import{parsePersonalAttachmentReferences as q}from"./attachment-reference.js";import{assertFinishedAttachmentReferences as R,markAttachmentReferences as T}from"./attachment-transfer-store.js";import{resolvePersonalSyncV1Path as $}from"../personal-sync/paths.js";import{getPersonalSyncIdentity as y}from"../personal-sync/epoch.js";import{getDaemonInstallationId as M}from"../personal-sync/identity.js";import{appendMessage as v,getMessageDeliveryStatus as D,recordSession as P,tombstoneMessage as F,upsertMessage as b}from"./store.js";import{requestSessionIndexPublish as S}from"./index-publisher.js";function O(){return{sessions:{}}}function C(){return new Date().toISOString()}function K(s){return`enqueue-send-${s}-${x()}`}function m(){try{const s=JSON.parse(p.readFileSync(E(),"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 O();throw s}}function I(s){const e=$();p.mkdirSync(e,{recursive:!0,mode:448});const n=E(),t=`${n}.tmp-${process.pid}-${x()}`;try{p.writeFileSync(t,`${JSON.stringify(s,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"});const i=p.openSync(t,"r");if(typeof i=="number")try{p.fsyncSync(i)}finally{p.closeSync(i)}p.renameSync(t,n)}finally{try{p.unlinkSync(t)}catch{}}}function E(){return $("outbound-queue.json")}function A(s){const e=C();return{...y(M()),id:s.queueMessageId||s.clientMessageId||`queue-${x()}`,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:q(s.attachments),externalChannel:s.externalChannel??null,replyTarget:s.replyTarget??null,origin:s.origin,createdAt:e,updatedAt:e}}function k(s){return{id:s.clientMessageId??s.id,sessionId:s.sessionId,role:"user",ts:Date.parse(s.createdAt),payload:j(s.text,s.attachments)}}class de{opts;draining=new Set;prepareManagedRoomTurn=null;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{...y(M()),sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:(m().sessions[e]??[]).map(W)}}setManagedRoomTurnPreparer(e){this.prepareManagedRoomTurn=e}async enqueueManagedRoomBatch(e){if(e.length===0)return;const n=this.opts.getRuntime(),t=m(),i=new Set,a=new Map;let d=!1;for(const o of e){const r=o.params;if(!r.sessionId||!r.text||!r.agentType||!r.workDir)throw new Error("sessionId, text, agentType and workDir are required");const u=A({...y(M()),...r});u.origin="room",u.managedRoom=o.context;const l=t.sessions[r.sessionId]??[],g=Q(l,u),h=u.clientMessageId??u.id,f=D(r.sessionId,h);if(!g&&f.state==="not_found")l.push(u),t.sessions[r.sessionId]=l,a.set(`${u.sessionId}\0${h}`,u),i.add(r.sessionId),d=!0;else if(g&&f.state==="not_found")a.set(`${g.sessionId}\0${h}`,g),i.add(r.sessionId);else if(g&&f.state==="queued")i.add(r.sessionId);else if(g){const c=l.filter(_=>_.id!==g.id);c.length?t.sessions[r.sessionId]=c:delete t.sessions[r.sessionId],d=!0}}d&&I(t);for(const o of a.values())v(o.sessionId,k(o),"queued"),P({sessionId:o.sessionId,agentType:o.agentType,sessionMode:o.sessionMode,managerConfig:o.managerConfig,workDir:o.workDir,agentSessionId:o.agentSessionId,modelId:o.modelId});if(i.size!==0){S(n.client);for(const o of i)this.broadcast(o),this.drainNext(o)}}async handleEnqueue(e){const n=this.opts.getRuntime(),t=e.params;if(z(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),a=q(t.attachments);a?.length&&R(i,a);const d={text:t.text,attachments:a,localized:!1},o=await this.isSessionBusy(t.sessionId),r=m(),u=r.sessions[t.sessionId]??[];if(!o&&!u.length){await this.opts.dispatchReq({...e,id:K(e.id),method:"chat.send",params:{...t,text:d.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:d.attachments}});return}const l=A({...t,text:d.text,attachments:d.attachments}),g=l.clientMessageId??l.id,h=D(t.sessionId,g);if(h.state!=="not_found"){n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:h.state==="queued",queueMessageId:l.id,...h,queue:this.getSnapshot(t.sessionId)}});return}const f=Q(u,l);f||(r.sessions[t.sessionId]=[...u,l],I(r));const c=f??l;P({sessionId:c.sessionId,agentType:c.agentType,sessionMode:c.sessionMode,managerConfig:c.managerConfig,workDir:c.workDir,agentSessionId:c.agentSessionId,modelId:c.modelId}),v(c.sessionId,k(c),"queued"),T(n.resolvePath(c.workDir),c.attachments??[]),S(n.client),this.broadcast(t.sessionId),n.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:f?.id??l.id,...D(t.sessionId,f?.clientMessageId??f?.id??l.clientMessageId??l.id),queue:this.getSnapshot(t.sessionId),...d.localized?{localizedAttachments:!0}:{}}}),o||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=m(),a=i.sessions[t.sessionId]??[],d=a.findIndex(u=>u.id===t.queueMessageId);if(d<0){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}const o=q(t.attachments),r=n.resolvePath(a[d].workDir);o?.length&&R(r,o),a[d]={...a[d],text:t.text,attachments:o,updatedAt:C()},i.sessions[t.sessionId]=a,I(i),b(t.sessionId,k(a[d]),"queued"),a[d].attachments?.length&&T(r,a[d].attachments??[]),S(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=m(),a=i.sessions[t.sessionId]??[],d=a.find(r=>r.id===t.queueMessageId),o=a.filter(r=>r.id!==t.queueMessageId);if(o.length===a.length){n.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}o.length?i.sessions[t.sessionId]=o:delete i.sessions[t.sessionId],I(i),F(t.sessionId,d?.clientMessageId??d?.id??t.queueMessageId),S(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){J(e),this.draining.delete(e)}drainIdleQueues(){const e=m();for(const n of Object.keys(e.sessions))this.drainNext(n)}async drainNext(e){if(!this.draining.has(e)){this.draining.add(e);try{if(await this.isSessionBusy(e))return;const t=m().sessions[e]??[],i=U(t);if(i.length===0){this.broadcast(e);return}for(const r of i)b(e,k(r),"queued");await this.dispatchQueuedMessages(i);const a=m(),d=new Set(i.map(r=>r.id)),o=(a.sessions[e]??[]).filter(r=>!d.has(r.id));o.length?a.sessions[e]=o:delete a.sessions[e],I(a),this.broadcast(e)}catch(n){console.error(`[chat.queue] dispatch failed sessionId=${e}: ${n instanceof Error?n.message:String(n)}`),this.broadcast(e)}finally{this.draining.delete(e)}}}async dispatchQueuedMessages(e){const n=e[0],t=e.flatMap(o=>o.managedRoom?[o.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 a=e.length===1?n.text:L(e),d=H(e.flatMap(o=>o.attachments??[]));await this.opts.dispatchQueuedReq({type:"req",id:`queue-send-${n.id}-${Date.now()}`,method:"chat.send",params:{...y(M()),sessionId:n.sessionId,text:a,agentType:n.agentType,sessionMode:n.sessionMode,managerConfig:n.managerConfig,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??void 0,systemPrompt:i?.systemPrompt??n.systemPrompt??null,managerDefaultWorkerAgentType:n.managerDefaultWorkerAgentType,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(W),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 J(s){const e=m();e.sessions[s]&&(delete e.sessions[s],I(e))}function Q(s,e){const n=w(e);if(n)return s.find(i=>w(i)===n)??null;if(e.origin!=="external")return null;const t=B(e);return s.find(i=>B(i)===t)??null}function W(s){const{origin:e,managedRoom:n,...t}=s;return t}function U(s){const e=s[0];if(!e)return[];const n=N(e),t=[];for(const i of s){if(N(i)!==n)break;t.push(i)}return t}function N(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 L(s){return s.every(e=>e.managedRoom)?`[\u7FA4\u6D88\u606F]
3
+ ${s.map(e=>G(e.text)).filter(Boolean).join(`
3
4
 
4
- `),attachments:J(i.flatMap(o=>o.attachments??[])),updatedAt:k()}}async dispatchQueuedMessage(e){await this.opts.dispatchReq({type:"req",id:`queue-send-${e.id}-${Date.now()}`,method:"chat.send",params:{...M(x()),sessionId:e.sessionId,text:e.text,agentType:e.agentType,sessionMode:e.sessionMode,managerConfig:e.managerConfig,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??void 0,systemPrompt:e.systemPrompt??null,managerDefaultWorkerAgentType:e.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:e.managerDefaultWorkerModelId,reasoningEffort:e.reasoningEffort??void 0,clientMessageId:e.clientMessageId??e.id,attachments:e.attachments,externalChannel:e.externalChannel,replyTarget:e.replyTarget,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 F(s){const e=f();e.sessions[s]&&(delete e.sessions[s],p(e))}function O(s){const e=new Set,n=[];for(const t of s){const i=D(t);e.has(i)||(e.add(i),n.push(t))}return n}function U(s,e){const n=w(e);if(n)return s.find(i=>w(i)===n)??null;if(e.origin!=="external")return null;const t=D(e);return s.find(i=>D(i)===t)??null}function D(s){const e=w(s);return e?`id:${e}`:["body",G(s.text),...(s.attachments??[]).map(n=>n.path||`${n.name}:${n.mimeType}`)].join(`
5
- `)}function w(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 J(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 G(s){return String(s||"").replace(/\s+/g," ").trim()}export{ne as ChatQueueManager,F as purgeQueuedSessionData};
5
+ `)}`:`[\u8FDE\u7EED\u7528\u6237\u6D88\u606F]
6
+ ${s.map((e,n)=>`<\u6D88\u606F ${n+1}>
7
+ ${e.text.trim()}`).join(`
8
+
9
+ `)}`}function G(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 B(s){const e=w(s);return e?`id:${e}`:["body",V(s.text),...(s.attachments??[]).map(n=>n.path||`${n.name}:${n.mimeType}`)].join(`
12
+ `)}function w(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 H(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 V(s){return String(s||"").replace(/\s+/g," ").trim()}export{de as ChatQueueManager,J as purgeQueuedSessionData};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.3.72",
3
+ "version": "0.3.73",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {