shennian 0.4.76 → 0.4.78

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.
@@ -496,8 +496,8 @@
496
496
  },
497
497
  {
498
498
  "file": "src/index.js",
499
- "beforeBytes": 41807,
500
- "afterBytes": 20748
499
+ "beforeBytes": 41850,
500
+ "afterBytes": 20757
501
501
  },
502
502
  {
503
503
  "file": "src/log-redaction.js",
@@ -526,13 +526,13 @@
526
526
  },
527
527
  {
528
528
  "file": "src/manager/runtime.js",
529
- "beforeBytes": 35179,
530
- "afterBytes": 17108
529
+ "beforeBytes": 35299,
530
+ "afterBytes": 17196
531
531
  },
532
532
  {
533
533
  "file": "src/manager/worker-ipc.js",
534
- "beforeBytes": 20466,
535
- "afterBytes": 11563
534
+ "beforeBytes": 20321,
535
+ "afterBytes": 11379
536
536
  },
537
537
  {
538
538
  "file": "src/manager/worker-markdown.js",
@@ -671,8 +671,8 @@
671
671
  },
672
672
  {
673
673
  "file": "src/room/activation-service.js",
674
- "beforeBytes": 27657,
675
- "afterBytes": 14988
674
+ "beforeBytes": 27554,
675
+ "afterBytes": 14802
676
676
  },
677
677
  {
678
678
  "file": "src/room/attachment-cache.js",
@@ -956,8 +956,8 @@
956
956
  },
957
957
  {
958
958
  "file": "src/session/manager.js",
959
- "beforeBytes": 42482,
960
- "afterBytes": 22159
959
+ "beforeBytes": 42645,
960
+ "afterBytes": 22256
961
961
  },
962
962
  {
963
963
  "file": "src/session/native-cleanup-outbox.js",
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{registerScheduleCommand as Se}from"./commands/schedule.js";import{resolvePersonalScheduleContext as ye}from"./schedules/personal-context.js";import{executeServerSchedule as oe,deliverServerSchedule as we}from"./schedules/server-client.js";import{Command as Ce}from"commander";import A from"chalk";import be from"node:os";import B from"node:fs";import{loadConfig as p,saveConfig as ke,getConfigPath as Re,getShennianDir as Pe,resolveShennianPath as F,normalizeAutoUpgradePolicy as Ae}from"./config/index.js";import{CliRelayClient as Ee}from"./relay/client.js";import{collectKnownLogSecrets as ne,installCliLogRedaction as Te,sanitizeCliLogValue as De}from"./log-redaction.js";import{registerPairCommand as Ue,runSmartStart as Me}from"./commands/pair.js";import{registerLogoutCommand as _e}from"./commands/logout.js";import{clearDaemonPidIfOwner as te,createDaemonInstanceId as xe,clearDaemonLauncher as re,findRunningDaemonProcessIds as $e,isRemoteAccessDisabled as ae,prepareRemoteAccessPause as Ne,stopPreparedRemoteAccessService as Oe,isIsolatedDaemonE2eInstance as Ve,getDaemonStatus as ie,registerDaemonCommand as Le,writeDaemonPid as se,writeDaemonLauncher as ce}from"./commands/daemon.js";import{registerRuntimeCommand as je}from"./commands/runtime.js";import{registerAgentCommand as He}from"./commands/agent.js";import{registerManagerCommand as Be}from"./commands/manager.js";import{registerWorkerCommand as Fe}from"./commands/worker.js";import{handleUpgradeStart as Ge,registerUpgradeCommand as We}from"./commands/upgrade.js";import{registerToolsCommand as qe}from"./commands/tools.js";import{registerIntegrationCommand as ze}from"./commands/integration.js";import{registerRoomCommand as Je}from"./commands/room.js";import{SessionManager as Ke}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as Ye}from"./session/handlers/project-fs.js";import{SERVERS as G,regionToUrl as Qe,urlToRegion as Ze}from"./region.js";import{clearUpgradeAttempt as Xe,getCurrentVersion as de,handleStartupCrashCheck as eo,readUpgradeAttempt as oo}from"./upgrade/engine.js";import{AutoUpgradeScheduler as no}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as to}from"./upgrade/updater-bootstrap.js";import{isDesktopDistribution as ro}from"./runtime-distribution.js";import{launchUpdater as ao,markUpdaterHealthy as io}from"./upgrade/updater-launcher.js";import{detectAgents as so}from"./agents/detect.js";import{augmentProcessPath as le}from"./env-path.js";import{acquireDaemonInstanceLock as co}from"./commands/daemon-instance-lock.js";le();const W=de();import{getCachedAgentInfos as q,resolveAgentInfos as lo}from"./agents/model-registry.js";import{initCliLogReporter as mo,reportLog as E,reportUpgradeAggregateMetric as po}from"./log-reporter.js";import{isNativeFusionEnabled as go}from"./native-fusion/config.js";import{NativeSessionFusionService as uo}from"./native-fusion/service.js";import{startDaemonLogRetention as fo}from"./daemon-log.js";import{startDaemonIpcServer as me}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as ho,resolveManagedRoomIpcPaths as vo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as Io}from"./daemon-ipc/control.js";import{createAgentReadinessHandlers as So}from"./daemon-ipc/agent-readiness.js";import{scanMachineAgents as yo}from"./session/handlers/agents.js";import{validateAgentWorkspaceConfiguration as wo}from"./session/handlers/agent-workspace-validation.js";import{DAEMON_IPC_VERSION as Co}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as bo}from"./room/session-registry.js";import{HttpRoomBindingClient as ko}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Ro}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Po}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Ao}from"./room/mirror-sync-service.js";import{NativeSourceDeletionStore as Eo}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as To,isCurrentRegistration as Do}from"./room/registration-authority.js";import{RoomActivationService as Uo,resolveManagedAgentPolicy as Mo}from"./room/activation-service.js";import{RoomActivationAudit as _o}from"./room/activation-audit.js";import{RoomAttachmentCache as xo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as $o}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as No}from"./room/managed-context.js";import{createManagedRoomExecutor as Oo}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as pe,SESSION_SYNC_ADVERTISED_CAPABILITIES as ge}from"@shennian/wire";import{getDaemonInstallationId as Vo}from"./session/store.js";import{synchronizeMachinePersonalAuthority as Lo}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as ue,ensurePersonalDataEpoch as jo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Ho,DeletionSyncTerminalError as Bo}from"./session/deletion-sync-state.js";const fe=Pe(),Fo=F("daemon.pid"),Go=F("daemon.instance.lock");function Wo(){try{const a=B.readFileSync(Fo,"utf-8").trim();if(a.startsWith("{")){const g=JSON.parse(a),c=Number(g.pid);return Number.isInteger(c)&&c>0?c:null}const s=parseInt(a,10);return Number.isInteger(s)&&s>0?s:null}catch{return null}}function qo(a){return a.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function z(){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 zo(a){const s=[`trigger=${a.trigger??"unknown"}`,`phase=${a.phase}`,`code=${a.code??"unknown"}`,`attempt=${a.reconnectAttempt}`];return a.reason&&s.push(`reason=${a.reason}`),a.error&&s.push(`error=${a.error}`),s.join(" ")}const i=new Ce;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(W),i.enablePositionalOptions(),ze(i),Je(i),Se(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",be.hostname()).action(async a=>{const s=p(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await Me(g,a.name)}),i.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async a=>{const s=F("env.json");try{const e=JSON.parse(B.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);le()}catch{}Te(ne(process.env,p()));const g=fo();ae()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(g),process.exit(0));const c=xe(),y=co({lockPath:Go,instanceId:c});y.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(y.ownerPid?` (PID ${y.ownerPid})`:"")+", skipping duplicate start"),clearInterval(g),process.exit(0));try{const e=Wo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),y.release(),process.exit(0)}catch{}}catch{}const J=Ve(process.env,fe)?[]:$e(process.pid);J.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${J[0]}), skipping duplicate start`),y.release(),process.exit(0)),se(process.pid,c,{version:W}),ce(process.pid,void 0,c),process.on("exit",()=>{te(process.pid,c),re(c)}),process.on("exit",y.release);let N=null;try{ro()||(N=to())}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const T=await eo();if(T){N||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{ao({fromVersion:W,targetVersion:T.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:T.jobId},{readiness:N}),console.error(`[upgrade] New version crashed ${T.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{jo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const l=p(),u=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??l.serverUrl??G.cn.url,h=de(),m=await so({serverUrl:u,authToken:l.machineToken??l.accessToken}),w=m.map(e=>e.type),O=q(m),f=new bo,D=new Po,C=new No(f),I=new ko(u),ve=new xo({actionClient:I}),Ie=new $o({actionClient:I});let S=null,r;const K=Oo({authority:C,actionClient:I,attachmentCache:ve,attachmentUploader:Ie,observations:D,getMachineCredentials:()=>({machineToken:p().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),scheduleExecutor:(e,n,o)=>oe({serverUrl:u,machineToken:p().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),C.revokeSession(e),r?.discardPendingManagedRoomMessages(n)}});let V=null,L=null,j=null;const b=await me({paths:ho(),handlers:{...Io({daemonVersion:h,protocolVersion:Co,getStatus:()=>ie(),getAgents:()=>m,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!V)throw new Error("upgrade_unavailable");return V(e)},prepareExternalUpgrade:e=>{if(!L)throw new Error("external_upgrade_unavailable");return L(e)},cancelExternalUpgrade:()=>{if(!j)throw new Error("external_upgrade_unavailable");return j()}}),...So({getStatus:()=>ie(),getSnapshot:()=>q(m).map(e=>{const n=m.find(o=>o.type===e.type);return{...e,runtimePlatform:n?.runtimePlatform,runtimeIsWsl:n?.runtimeIsWsl}}),discover:async e=>{if(!r)throw new Error("daemon_starting");const n=await yo(!0,e);if(e){const t=m.filter(P=>P.type!==e);m.splice(0,m.length,...t,...n.detectedAgents)}else m.splice(0,m.length,...n.detectedAgents);const o=q(m);return d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:m.map(t=>t.type),agents:o}}),n.agents.map(t=>{const P=n.detectedAgents.find(H=>H.type===t.type);return{...t,runtimePlatform:P?.runtimePlatform,runtimeIsWsl:P?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return wo(e,{ignoreNativeSession:(n,o)=>R?.ignoreNativeSource(n,o)})}})}});let k;try{k=await me({paths:vo(),handlers:{"managed-room.execute":e=>K(e),"schedule.personal":async e=>{const n=e,o=ye(n?.context);if(f.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await oe({serverUrl:u,machineToken:p().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let Y=!1;const U=()=>{Y||(Y=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",U),process.once("SIGTERM",U),!l.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",U),process.removeListener("SIGTERM",U);const Q=`${qo(u)}/relay/machine`,M=l.machineToken;if(!M)return;try{await Lo({serverUrl:u,machineToken:M,machineId:l.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),k.close()]),process.exit(1)}l.machineId&&mo(u,l.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${Q}... (v${h}) agents: ${w.join(",")}`),E({level:"info",wsEvent:"daemon.start",metadata:{version:h,agents:w}});let R=null,_=null,x=0,v=oo();const Z=Ae(l.autoUpgrade),d=new Ee({serverUrl:Q,machineToken:M,cliVersion:h,agentList:w,autoUpgradePolicy:Z,sessionSyncProtocolVersion:pe,sessionSyncCapabilities:ge,daemonInstallationId:Vo(),dataEpoch:ue,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),E({level:"info",wsEvent:"daemon.connected"}),O.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:O}}),lo(m,{serverUrl:l.serverUrl??u,authToken:l.machineToken??l.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(O)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:o}})}).catch(()=>{}),v&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:v.from,to:v.to,...v.jobId?{jobId:v.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),$.start(),_?.start();const e=++x,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),v){const o=v;v=null,o.jobId&&io(o.jobId,o.to),Xe(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),po({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),d.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===x&&d.getState()==="connected"&&R?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===x&&d.getState()==="connected"){if(o instanceof Bo)return;const t=o instanceof Ho?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{x+=1,r?.revokeProjectFsHandles(),_?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${zo(e)}`),E({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||r?.revokeProjectFsHandles()},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),E({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),r.handleReq(e)}});R=go()?new uo(d):null,_=new Ao({registry:f,observations:D,actionClient:I,getMachineCredentials:()=>({machineToken:p().machineToken}),isSessionDeleted:e=>new Eo().hasSession(e),onMessages:async(e,n,o,t)=>{await S?.processMessages(e,n,o,t)},onBindingPaused:e=>{C.revokeSession(e.registryKey),e.lifecycleState==="closed"?r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")}):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const $=new no({currentVersion:h,machineId:l.machineId??"unknown-machine",serverUrl:l.serverUrl??u,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:pe,dataEpoch:ue,advertisedCapabilities:ge,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(d,"auto-upgrade",e,{currentVersion:h,prepareForUpgrade:t=>r.prepareForUpgrade(t),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:n?void 0:()=>r.getUpgradeIdleState().idle})}});$.setPolicy(Z),r=new Ke(d,R,h,$,new Ro({registry:f,observations:D,bindingClient:I,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Ne(),isDisabled:()=>ae(),shutdownAfterResponse:()=>{setTimeout(()=>{Oe(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>C.revokeToken(e),K,null,Ye(M)),V=async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof n.targetVersion=="string"?n.targetVersion:void 0,t=n.confirmedMajor===!0;return await Ge(d,`local-upgrade-${Date.now()}`,o,{currentVersion:h,confirmedMajor:t,prepareForUpgrade:H=>r.prepareForUpgrade(H),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},L=e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof n.targetVersion=="string"?n.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...r.prepareForUpgrade(t)}},j=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),S=new Uo({registry:f,observations:D,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=p().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const t=await I.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});if(!Do(f,e)||!To(e,t))throw new Error("managed_room_binding_unavailable");return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,n,o)=>{const t=p().machineToken;if(!t)throw new Error("machine_not_authorized");return C.issue({registryKey:e,machineCredential:t,activationCauseMessageId:n,allowedActions:o})},audit:new _o,resolveManagedAgentPolicy:e=>Mo({...e,runtime:m.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>we({serverUrl:u,machineToken:p().machineToken,req:e,client:d,queue:r.getScheduleQueue(),registry:f}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(f.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await I.status({machineToken:p().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>S.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,n)=>S.prepareDirectConversationTurn(e,n)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?S.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[]}):e.source==="manager_event"?S.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:S.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),B.mkdirSync(fe,{recursive:!0}),se(process.pid,c,{version:h}),ce(process.pid,void 0,c),d.connect(),process.stdin.resume();let X=!1;const ee=async()=>{if(!X){X=!0,console.log(A.gray(`
3
- Disconnecting...`)),E({level:"info",wsEvent:"daemon.stop"}),clearInterval(g);try{await r.cleanup(),$.stop(),R?.stop(),_?.stop(),d.disconnect(),await Promise.allSettled([b.close(),k.close()]),te(process.pid,c),re(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{ee()}),process.on("SIGTERM",()=>{ee()})}),i.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>z()),i.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>z()),i.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>z());const he=i.command("config").description("View or modify configuration");he.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${Re()}`);const a=p(),s=De(a,ne(process.env,a));if(a.serverUrl){const g=Ze(a.serverUrl);console.log(`Region: ${g} (${G[g].label})`)}console.log(JSON.stringify(s,null,2))}),he.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((a,s)=>{const g=p();if(a==="server"){s!=="cn"&&s!=="global"&&(console.error(A.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=s;g.serverUrl=Qe(c),ke(g),console.log(A.green(`\u2713 Server set to ${G[c].label}`)),console.log(A.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(A.red(`\u2717 Unknown config key: ${a}. Supported: server`)),process.exit(1)}),Ue(i),_e(i),Le(i),je(i),He(i),Be(i),Fe(i),We(i),qe(i),i.parse();
1
+ import{registerScheduleCommand as Se}from"./commands/schedule.js";import{resolvePersonalScheduleContext as ye}from"./schedules/personal-context.js";import{executeServerSchedule as oe,deliverServerSchedule as we}from"./schedules/server-client.js";import{Command as Ce}from"commander";import A from"chalk";import be from"node:os";import F from"node:fs";import{loadConfig as p,saveConfig as ke,getConfigPath as Re,getShennianDir as Pe,resolveShennianPath as G,normalizeAutoUpgradePolicy as Ae}from"./config/index.js";import{CliRelayClient as Ee}from"./relay/client.js";import{collectKnownLogSecrets as ne,installCliLogRedaction as Te,sanitizeCliLogValue as De}from"./log-redaction.js";import{registerPairCommand as Ue,runSmartStart as Me}from"./commands/pair.js";import{registerLogoutCommand as _e}from"./commands/logout.js";import{clearDaemonPidIfOwner as te,createDaemonInstanceId as xe,clearDaemonLauncher as re,findRunningDaemonProcessIds as $e,isRemoteAccessDisabled as ae,prepareRemoteAccessPause as Ne,stopPreparedRemoteAccessService as Oe,isIsolatedDaemonE2eInstance as Ve,getDaemonStatus as ie,registerDaemonCommand as Le,writeDaemonPid as se,writeDaemonLauncher as ce}from"./commands/daemon.js";import{registerRuntimeCommand as je}from"./commands/runtime.js";import{registerAgentCommand as He}from"./commands/agent.js";import{registerManagerCommand as Be}from"./commands/manager.js";import{registerWorkerCommand as Fe}from"./commands/worker.js";import{handleUpgradeStart as Ge,registerUpgradeCommand as We}from"./commands/upgrade.js";import{registerToolsCommand as qe}from"./commands/tools.js";import{registerIntegrationCommand as ze}from"./commands/integration.js";import{registerRoomCommand as Je}from"./commands/room.js";import{SessionManager as Ke}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as Ye}from"./session/handlers/project-fs.js";import{SERVERS as W,regionToUrl as Qe,urlToRegion as Ze}from"./region.js";import{clearUpgradeAttempt as Xe,getCurrentVersion as de,handleStartupCrashCheck as eo,readUpgradeAttempt as oo}from"./upgrade/engine.js";import{AutoUpgradeScheduler as no}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as to}from"./upgrade/updater-bootstrap.js";import{isDesktopDistribution as ro}from"./runtime-distribution.js";import{launchUpdater as ao,markUpdaterHealthy as io}from"./upgrade/updater-launcher.js";import{detectAgents as so}from"./agents/detect.js";import{augmentProcessPath as le}from"./env-path.js";import{acquireDaemonInstanceLock as co}from"./commands/daemon-instance-lock.js";le();const q=de();import{getCachedAgentInfos as N,resolveAgentInfos as lo}from"./agents/model-registry.js";import{initCliLogReporter as mo,reportLog as E,reportUpgradeAggregateMetric as po}from"./log-reporter.js";import{isNativeFusionEnabled as go}from"./native-fusion/config.js";import{NativeSessionFusionService as uo}from"./native-fusion/service.js";import{startDaemonLogRetention as fo}from"./daemon-log.js";import{startDaemonIpcServer as me}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as ho,resolveManagedRoomIpcPaths as vo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as Io}from"./daemon-ipc/control.js";import{createAgentReadinessHandlers as So}from"./daemon-ipc/agent-readiness.js";import{scanMachineAgents as yo}from"./session/handlers/agents.js";import{validateAgentWorkspaceConfiguration as wo}from"./session/handlers/agent-workspace-validation.js";import{DAEMON_IPC_VERSION as Co}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as bo}from"./room/session-registry.js";import{HttpRoomBindingClient as ko}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Ro}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Po}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Ao}from"./room/mirror-sync-service.js";import{NativeSourceDeletionStore as Eo}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as To,isCurrentRegistration as Do}from"./room/registration-authority.js";import{RoomActivationService as Uo,resolveManagedAgentPolicy as Mo}from"./room/activation-service.js";import{RoomActivationAudit as _o}from"./room/activation-audit.js";import{RoomAttachmentCache as xo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as $o}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as No}from"./room/managed-context.js";import{createManagedRoomExecutor as Oo}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as pe,SESSION_SYNC_ADVERTISED_CAPABILITIES as ge}from"@shennian/wire";import{getDaemonInstallationId as Vo}from"./session/store.js";import{synchronizeMachinePersonalAuthority as Lo}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as ue,ensurePersonalDataEpoch as jo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Ho,DeletionSyncTerminalError as Bo}from"./session/deletion-sync-state.js";const fe=Pe(),Fo=G("daemon.pid"),Go=G("daemon.instance.lock");function Wo(){try{const a=F.readFileSync(Fo,"utf-8").trim();if(a.startsWith("{")){const g=JSON.parse(a),c=Number(g.pid);return Number.isInteger(c)&&c>0?c:null}const s=parseInt(a,10);return Number.isInteger(s)&&s>0?s:null}catch{return null}}function qo(a){return a.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function z(){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 zo(a){const s=[`trigger=${a.trigger??"unknown"}`,`phase=${a.phase}`,`code=${a.code??"unknown"}`,`attempt=${a.reconnectAttempt}`];return a.reason&&s.push(`reason=${a.reason}`),a.error&&s.push(`error=${a.error}`),s.join(" ")}const i=new Ce;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(q),i.enablePositionalOptions(),ze(i),Je(i),Se(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",be.hostname()).action(async a=>{const s=p(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await Me(g,a.name)}),i.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async a=>{const s=G("env.json");try{const e=JSON.parse(F.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);le()}catch{}Te(ne(process.env,p()));const g=fo();ae()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(g),process.exit(0));const c=xe(),y=co({lockPath:Go,instanceId:c});y.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(y.ownerPid?` (PID ${y.ownerPid})`:"")+", skipping duplicate start"),clearInterval(g),process.exit(0));try{const e=Wo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),y.release(),process.exit(0)}catch{}}catch{}const J=Ve(process.env,fe)?[]:$e(process.pid);J.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${J[0]}), skipping duplicate start`),y.release(),process.exit(0)),se(process.pid,c,{version:q}),ce(process.pid,void 0,c),process.on("exit",()=>{te(process.pid,c),re(c)}),process.on("exit",y.release);let O=null;try{ro()||(O=to())}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const T=await eo();if(T){O||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{ao({fromVersion:q,targetVersion:T.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:T.jobId},{readiness:O}),console.error(`[upgrade] New version crashed ${T.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{jo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const m=p(),u=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??m.serverUrl??W.cn.url,h=de(),d=await so({serverUrl:u,authToken:m.machineToken??m.accessToken}),w=d.map(e=>e.type),V=N(d),f=new bo,D=new Po,C=new No(f),I=new ko(u),ve=new xo({actionClient:I}),Ie=new $o({actionClient:I});let S=null,r;const K=Oo({authority:C,actionClient:I,attachmentCache:ve,attachmentUploader:Ie,observations:D,getMachineCredentials:()=>({machineToken:p().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),scheduleExecutor:(e,n,o)=>oe({serverUrl:u,machineToken:p().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),C.revokeSession(e),r?.discardPendingManagedRoomMessages(n)}});let L=null,j=null,H=null;const b=await me({paths:ho(),handlers:{...Io({daemonVersion:h,protocolVersion:Co,getStatus:()=>ie(),getAgents:()=>d,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!L)throw new Error("upgrade_unavailable");return L(e)},prepareExternalUpgrade:e=>{if(!j)throw new Error("external_upgrade_unavailable");return j(e)},cancelExternalUpgrade:()=>{if(!H)throw new Error("external_upgrade_unavailable");return H()}}),...So({getStatus:()=>ie(),getSnapshot:()=>N(d).map(e=>{const n=d.find(o=>o.type===e.type);return{...e,runtimePlatform:n?.runtimePlatform,runtimeIsWsl:n?.runtimeIsWsl}}),discover:async e=>{if(!r)throw new Error("daemon_starting");const n=await yo(!0,e);if(e){const t=d.filter(P=>P.type!==e);d.splice(0,d.length,...t,...n.detectedAgents)}else d.splice(0,d.length,...n.detectedAgents);const o=N(d);return l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:d.map(t=>t.type),agents:o}}),n.agents.map(t=>{const P=n.detectedAgents.find(B=>B.type===t.type);return{...t,runtimePlatform:P?.runtimePlatform,runtimeIsWsl:P?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return wo(e,{ignoreNativeSession:(n,o)=>R?.ignoreNativeSource(n,o)})}})}});let k;try{k=await me({paths:vo(),handlers:{"managed-room.execute":e=>K(e),"schedule.personal":async e=>{const n=e,o=ye(n?.context);if(f.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await oe({serverUrl:u,machineToken:p().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let Y=!1;const U=()=>{Y||(Y=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",U),process.once("SIGTERM",U),!m.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([b.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",U),process.removeListener("SIGTERM",U);const Q=`${qo(u)}/relay/machine`,M=m.machineToken;if(!M)return;try{await Lo({serverUrl:u,machineToken:M,machineId:m.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([b.close(),k.close()]),process.exit(1)}m.machineId&&mo(u,m.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${Q}... (v${h}) agents: ${w.join(",")}`),E({level:"info",wsEvent:"daemon.start",metadata:{version:h,agents:w}});let R=null,_=null,x=0,v=oo();const Z=Ae(m.autoUpgrade),l=new Ee({serverUrl:Q,machineToken:M,cliVersion:h,agentList:w,autoUpgradePolicy:Z,sessionSyncProtocolVersion:pe,sessionSyncCapabilities:ge,daemonInstallationId:Vo(),dataEpoch:ue,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),E({level:"info",wsEvent:"daemon.connected"}),V.some(o=>o.models.length>0)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:V}}),lo(d,{serverUrl:m.serverUrl??u,authToken:m.machineToken??m.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(V)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:o}})}).catch(()=>{}),v&&l.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:v.from,to:v.to,...v.jobId?{jobId:v.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),$.start(),_?.start();const e=++x,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),v){const o=v;v=null,o.jobId&&io(o.jobId,o.to),Xe(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),po({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),l.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&l.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===x&&l.getState()==="connected"&&R?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===x&&l.getState()==="connected"){if(o instanceof Bo)return;const t=o instanceof Ho?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{x+=1,r?.revokeProjectFsHandles(),_?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${zo(e)}`),E({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||r?.revokeProjectFsHandles()},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),E({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),r.handleReq(e)}});R=go()?new uo(l):null,_=new Ao({registry:f,observations:D,actionClient:I,getMachineCredentials:()=>({machineToken:p().machineToken}),isSessionDeleted:e=>new Eo().hasSession(e),onMessages:async(e,n,o,t)=>{await S?.processMessages(e,n,o,t)},onBindingPaused:e=>{C.revokeSession(e.registryKey),e.lifecycleState==="closed"?r?.retireManagedRoomSession(e.nianSessionId).catch(()=>{console.warn("[room.sync] retired session cleanup failed")}):r?.discardPendingManagedRoomMessages(e.nianSessionId)}});const $=new no({currentVersion:h,machineId:m.machineId??"unknown-machine",serverUrl:m.serverUrl??u,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:pe,dataEpoch:ue,advertisedCapabilities:ge,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(l,"auto-upgrade",e,{currentVersion:h,prepareForUpgrade:t=>r.prepareForUpgrade(t),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:n?void 0:()=>r.getUpgradeIdleState().idle})}});$.setPolicy(Z),r=new Ke(l,R,h,$,new Ro({registry:f,observations:D,bindingClient:I,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Ne(),isDisabled:()=>ae(),shutdownAfterResponse:()=>{setTimeout(()=>{Oe(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>C.revokeToken(e),K,null,Ye(M),()=>N(d)),L=async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof n.targetVersion=="string"?n.targetVersion:void 0,t=n.confirmedMajor===!0;return await Ge(l,`local-upgrade-${Date.now()}`,o,{currentVersion:h,confirmedMajor:t,prepareForUpgrade:B=>r.prepareForUpgrade(B),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},j=e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof n.targetVersion=="string"?n.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...r.prepareForUpgrade(t)}},H=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),S=new Uo({registry:f,observations:D,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=p().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const t=await I.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});if(!Do(f,e)||!To(e,t))throw new Error("managed_room_binding_unavailable");return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,n,o)=>{const t=p().machineToken;if(!t)throw new Error("machine_not_authorized");return C.issue({registryKey:e,machineCredential:t,activationCauseMessageId:n,allowedActions:o})},audit:new _o,resolveManagedAgentPolicy:e=>Mo({...e,runtime:d.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>we({serverUrl:u,machineToken:p().machineToken,req:e,client:l,queue:r.getScheduleQueue(),registry:f}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(f.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await I.status({machineToken:p().machineToken,roomId:e.roomId,bindingId:e.bindingId});return o.status==="active"&&o.room.status==="active"?"ready":"ended"},r.setManagedRoomTurnPreparer(e=>S.prepareConversationTurn(e)),r.setManagedAgentDirectTurnPreparer((e,n)=>S.prepareDirectConversationTurn(e,n)),r.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?S.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[]}):e.source==="manager_event"?S.prepareDirectConversationTurn(e.sessionId,{allowedRoomActions:[],managerEvent:!0}):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:S.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),F.mkdirSync(fe,{recursive:!0}),se(process.pid,c,{version:h}),ce(process.pid,void 0,c),l.connect(),process.stdin.resume();let X=!1;const ee=async()=>{if(!X){X=!0,console.log(A.gray(`
3
+ Disconnecting...`)),E({level:"info",wsEvent:"daemon.stop"}),clearInterval(g);try{await r.cleanup(),$.stop(),R?.stop(),_?.stop(),l.disconnect(),await Promise.allSettled([b.close(),k.close()]),te(process.pid,c),re(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{ee()}),process.on("SIGTERM",()=>{ee()})}),i.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>z()),i.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>z()),i.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>z());const he=i.command("config").description("View or modify configuration");he.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${Re()}`);const a=p(),s=De(a,ne(process.env,a));if(a.serverUrl){const g=Ze(a.serverUrl);console.log(`Region: ${g} (${W[g].label})`)}console.log(JSON.stringify(s,null,2))}),he.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((a,s)=>{const g=p();if(a==="server"){s!=="cn"&&s!=="global"&&(console.error(A.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=s;g.serverUrl=Qe(c),ke(g),console.log(A.green(`\u2713 Server set to ${W[c].label}`)),console.log(A.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(A.red(`\u2717 Unknown config key: ${a}. Supported: server`)),process.exit(1)}),Ue(i),_e(i),Le(i),je(i),He(i),Be(i),Fe(i),We(i),qe(i),i.parse();
@@ -1,5 +1,5 @@
1
1
  import type { AgentEvent, AgentAdapter } from '../agents/adapter.js';
2
- import { type AgentType, type ExternalChannelSessionStatus, type ReqFrame } from '@shennian/wire';
2
+ import { type AgentType, type AgentInfo, type ExternalChannelSessionStatus, type ReqFrame } from '@shennian/wire';
3
3
  import { ManagerRegistry, type ManagedWorkerRoomOrigin } from './registry.js';
4
4
  import type { SessionManagerRuntime } from '../session/types.js';
5
5
  import { RetiredChannelRuntime as ChannelRuntime, type ChannelRuntimeOptions, type WeChatAutomationLane } from './retired-external-channel-compat.js';
@@ -12,6 +12,7 @@ export type LocalReqDispatchResult = {
12
12
  export type LocalReqDispatcher = (req: ReqFrame) => Promise<void | LocalReqDispatchResult>;
13
13
  type ManagerRuntimeServiceOptions = {
14
14
  getRuntime: () => SessionManagerRuntime;
15
+ getAgentSnapshot?: () => AgentInfo[];
15
16
  dispatchReq: LocalReqDispatcher;
16
17
  channelRuntime?: ChannelRuntime;
17
18
  createWeChatRpaProductRunner?: ChannelRuntimeOptions['createWeChatRpaProductRunner'];
@@ -1,13 +1,13 @@
1
- import _ from"node:http";import{createHash as N,randomBytes as W,randomUUID as k}from"node:crypto";import p from"node:fs";import A from"node:os";import M from"node:path";import{ManagerRegistry as T}from"./registry.js";import{appendMessage as O}from"../session/store.js";import{requestSessionIndexPublish as q}from"../session/index-publisher.js";import{RetiredChannelRuntime as D}from"./retired-external-channel-compat.js";import{resolveShennianPath as P}from"../config/index.js";import{buildExternalChannelInstructions as C}from"../agents/external-channel-instructions.js";import{getPersonalSyncIdentity as w}from"../personal-sync/epoch.js";import{EphemeralWorkerError as B,handleEphemeralWorkerIpcRoute as H,handleWorkerIpcRoute as v}from"./worker-ipc.js";import{deriveWorkerTitle as $,formatWorkerMarkdown as U}from"./worker-markdown.js";import{markTrustedManagerEvent as S}from"../session/manager-event-authority.js";const j=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024),y=12*6e4;let b=null;function ge(d){b=d}function he(){return b}function f(d){return M.resolve(d||A.homedir())}function m(d,e,r){d.writeHead(e,{"content-type":"application/json; charset=utf-8"}),d.end(JSON.stringify(r))}function R(d){return d.replace(/\r\n/g,`
2
- `).trim()}function G(d){return d==="worker.final"?"completed":d==="worker.error"?"failed":d==="worker.aborted"?"stopped":"progress"}function F(d,e){return`manager-inbox:${N("sha256").update(d).update("\0").update(e.map(t=>t.id).join("\0")).digest("hex").slice(0,32)}`}async function J(d){const e=[];let r=0;for await(const t of d){const n=Buffer.isBuffer(t)?t:Buffer.from(t);if(r+=n.length,r>j)throw new Error("Request body too large");e.push(n)}return e.length===0?{}:JSON.parse(Buffer.concat(e).toString("utf8"))}function K(d,e,r,t){d.client.sendRes({type:"res",id:e,ok:r,...r?{payload:t}:{error:String(t.error||"unknown error")}})}function x(){return V()?M.join(A.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):P("runtime","manager-ipc.json")}function V(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class le{opts;managedPolicies=new Map;registry=new T;channelRuntime;server=null;ipcUrl=null;ipcToken=W(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;pendingWorkerOrigins=new Map;prepareWorkerTurnOrigin(e,r){r?this.pendingWorkerOrigins.set(e,r):this.pendingWorkerOrigins.delete(e)}constructor(e){this.opts=e,this.channelRuntime=e.channelRuntime??new D}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.registry.clearWorkerRunOrigins(),this.server=_.createServer((r,t)=>{this.handleIpc(r,t)}),this.server.requestTimeout=y,this.server.timeout=y,this.server.keepAliveTimeout=y,this.server.headersTimeout=y+5e3,await new Promise((r,t)=>{this.server.once("error",t),this.server.listen(0,"127.0.0.1",()=>r())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref(),this.recoverPersistedInboxDeliveries()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=x();p.mkdirSync(M.dirname(e),{recursive:!0}),p.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),p.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=x(),r=JSON.parse(p.readFileSync(e,"utf-8"));(r.url===this.ipcUrl||r.token===this.ipcToken)&&p.unlinkSync(e)}catch{}}getInjectedEnv(e,r,t,n){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:r??"",SHENNIAN_MANAGER_WORKDIR:f(t),SHENNIAN_MANAGER_MODEL:n,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:f(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null,managedRoomBound:e.managedRoomBound??(this.managedPolicies.has(e.sessionId)||void 0)})}setManagedAgentPolicy(e,r){if(r){this.managedPolicies.set(e,r);const t=this.registry.getManager(e);t&&!t.managedRoomBound&&this.registry.upsertManager({...t,managedRoomBound:!0})}else this.managedPolicies.delete(e)}getManagedAgentPolicyForWorker(e,r){const t=this.registry.getManager(e),n=this.managedPolicies.get(e);return!t||!n||f(r)!==t.workDir?null:n}isExternalWorkerController(e,r){const t=this.registry.getManager(e);return t?.controllerKind==="ephemeral"&&f(r)===t.workDir}isManagedWorkerSession(e){return!!this.findWorker(e)}isManagedRoomBoundManager(e){return this.registry.getManager(e)?.managedRoomBound===!0}async retireManagerAndWorkers(e){const r=this.registry.retireManager(e);this.managedPolicies.delete(e);const t=this.opts.getRuntime(),n=[],a=async(s,u)=>{const i=[];try{t.runJournal?.markSessionAborted(s)}catch(h){i.push(h)}try{t.recoveryBarrier?.end(s)}catch(h){i.push(h)}try{t.chatQueue?.purgeSession(s)}catch(h){i.push(h)}const o=t.sessions.get(s);let c=!o;if(o){if(o.managedRoomContextToken)try{t.revokeManagedRoomContextToken?.(o.managedRoomContextToken),o.managedRoomContextToken=null}catch(h){i.push(h)}try{await o.adapter.stop(),c=!0}catch(h){i.push(h)}}if(o&&c&&i.length===0&&(t.sessions.delete(s),o.heartbeatTimer&&clearInterval(o.heartbeatTimer),o.adapter.removeAllListeners()),c&&i.length===0)try{u?.()}catch(h){i.push(h),n.push(h)}else n.push(...i)};await a(e);for(const s of r)await a(s.sessionId,()=>this.registry.completeRetiredWorker(e,s.sessionId));if(n.length)throw new AggregateError(n,"Failed to retire every Manager or Worker session");return r.map(s=>s.sessionId)}setManagerWorkerDefaults(e,r,t){const n=this.registry.getManager(e);n&&this.registry.upsertManager({...n,defaultWorkerAgentType:r??null,defaultWorkerModelId:t??null})}getManagerWorkerDefaults(e){const r=this.registry.getManager(e);return{agentType:r?.defaultWorkerAgentType??null,modelId:r?.defaultWorkerModelId??null}}noteManagerAgentSession(e,r,t,n){const a=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:r,workDir:f(t),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:n,agentType:a?.agentType,providerModelId:a?.providerModelId})}noteAgentEvent(e,r){const t=this.findWorker(e);if(!t)return;const n={lastActivityAt:new Date().toISOString(),runId:r.runId};if(r.agentSessionId&&(n.agentSessionId=r.agentSessionId),r.state==="start"){const i=this.pendingWorkerOrigins.get(e);this.pendingWorkerOrigins.delete(e),i&&(n.runRoomOrigins={...t.runRoomOrigins??{},[r.runId]:i})}const a=`${e}:${r.runId}`;let s=r.message||t.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)";if(r.state==="delta"&&r.text&&!r.thinking){const i=(this.workerTextAcc.get(a)??"")+r.text;this.workerTextAcc.set(a,i);const o=R(i);o&&(n.summary=o.length>160?`${o.slice(0,160)}...`:o)}if(r.state==="final"||r.state==="error"||r.state==="aborted"){n.status=r.state;const i=R(this.workerTextAcc.get(a)??"");if(i&&(n.summary=i.length>240?`${i.slice(0,240)}...`:i,s=r.state==="final"?i:r.message||i),this.workerTextAcc.delete(a),t.runRoomOrigins?.[r.runId]){const o={...t.runRoomOrigins};delete o[r.runId],n.runRoomOrigins=o}}else r.state==="start"&&(n.status="running");const u=this.registry.updateWorker(e,n);u&&r.errorCode==="auth_retrying"&&this.registry.enqueueInboxEvent({managerSessionId:u.managedBy,workerSessionId:u.sessionId,kind:"worker.health",priority:"normal",runId:r.runId,roomOrigin:t.runRoomOrigins?.[r.runId]??null,summary:r.message||"Worker authentication is retrying.",code:"auth_retrying"}),u&&(r.state==="final"||r.state==="error"||r.state==="aborted")&&this.enqueueWorkerEvent(u.managedBy,u,r.state,r.runId,s,t.runRoomOrigins?.[r.runId]??null,r.errorCode==="auth_required"||r.errorCode==="acp_auth_required"?"auth_required":void 0)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){K(this.opts.getRuntime(),e.id,!1,{error:"feature_retired"})}async handleIpc(e,r){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){m(r,401,{ok:!1,error:"Unauthorized"});return}try{const t=new URL(e.url??"/","http://127.0.0.1"),n=await J(e);if(t.pathname.startsWith("/channel/")||t.pathname.startsWith("/external/")||t.pathname.startsWith("/wechat-rpa/")){m(r,410,{ok:!1,error:"feature_retired"});return}const a={registry:this.registry,getRuntime:this.opts.getRuntime,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},s=await H(a,t.pathname,n)??await v(a,t.pathname,n,String(e.headers["x-shennian-manager-session-id"]||""));if(s){m(r,s.status,s.body);return}m(r,404,{ok:!1,error:`Unknown manager IPC path: ${t.pathname}`})}catch(t){if(t instanceof B){m(r,t.status,{ok:!1,error:t.code,message:t.message,nextAction:t.nextAction});return}m(r,400,{ok:!1,error:t instanceof Error?t.message:String(t)})}}async executeScopedWorkerAction(e,r,t,n){const a={"manager.sessions.list":"/sessions/list","manager.sessions.start":"/sessions/start","manager.sessions.send":"/sessions/send","manager.sessions.queue":"/sessions/queue","manager.sessions.queue.edit":"/sessions/queue/edit","manager.sessions.queue.delete":"/sessions/queue/delete","manager.sessions.stop":"/sessions/stop","manager.sessions.read":"/sessions/read","manager.memory.path":"/memory/path"},s=await v({registry:this.registry,getRuntime:this.opts.getRuntime,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},a[r],t,e,{roomOrigin:n});if(!s||s.status<200||s.status>=300)throw new Error(String(s?.body.error||"Managed Manager action failed"));return JSON.parse(JSON.stringify(s.body))}async dispatchChatEnqueue(e,r,t,n,a,s,u,i){if((await this.opts.dispatchReq(S({type:"req",id:`manager-enqueue-${k()}`,method:"chat.enqueue",params:{...w(),sessionId:e,text:n,agentType:r,workDir:t,agentSessionId:a,modelId:s,managedWorkerParentSessionId:u,managerWorkerRoomOrigin:i}})))?.durableQueueHandoff===!1)throw new Error("manager_worker_queue_handoff_unconfirmed")}appendManagerVisibleMarkdown(e,r,t){this.registry.getManager(e)?.controllerKind==="managed_session"&&(O(e,{id:t,sessionId:e,role:"agent",ts:Date.now(),payload:r},"committed"),q(this.opts.getRuntime().client))}enqueueWorkerEvent(e,r,t,n,a,s,u){const i=this.registry.enqueueInboxEvent({managerSessionId:e,workerSessionId:r.sessionId,kind:t==="final"?"worker.final":t==="error"?"worker.error":"worker.aborted",priority:"normal",runId:n??null,roomOrigin:s??null,summary:a||r.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)",...u?{code:u}:{}}),o=this.registry.getManager(e);i&&o?.controllerKind==="managed_session"&&this.drainManagerInbox(e)}scanWorkerHealth(){const e=Date.now(),r=this.registry.load(),t=this.opts.getRuntime();for(const n of Object.values(r.workers)){if(n.status!=="running")continue;const a=e-Date.parse(n.createdAt);if(a<10*6e4)continue;const s=e-Date.parse(n.lastActivityAt),u=t.sessions.get(n.sessionId)?.currentRunId,i=!!t.chatQueue?.getSnapshot(n.sessionId).pending.length;if(!u){if(!i&&s>=10*6e4){const E="\u672A\u627E\u5230\u6D3B\u52A8\u4E2D\u7684 Provider Run \u6216\u5F85\u5904\u7406\u6D88\u606F\uFF1B\u5DF2\u5C06\u9057\u7559\u7684\u8FD0\u884C\u72B6\u6001\u6536\u655B\u4E3A\u5931\u8D25\u3002\u539F Worker \u4F1A\u8BDD\u4ECD\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002",I=this.registry.updateWorker(n.sessionId,{status:"error",lastActivityAt:new Date(e).toISOString(),summary:E});I&&this.enqueueWorkerEvent(I.managedBy,I,"error",n.runId??void 0,E)}continue}const o=n.healthNotifiedAt?Date.parse(n.healthNotifiedAt):0;if(o&&e-o<10*6e4)continue;const c=r.managers[n.managedBy];if(!c)continue;const h=Math.max(0,Math.floor(s/6e4)),g=R(n.runId?this.workerTextAcc.get(`${n.sessionId}:${n.runId}`)??"":""),l=`\u5DF2\u8FD0\u884C ${Math.floor(a/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
1
+ import _ from"node:http";import{createHash as N,randomBytes as W,randomUUID as k}from"node:crypto";import p from"node:fs";import A from"node:os";import M from"node:path";import{ManagerRegistry as T}from"./registry.js";import{appendMessage as O}from"../session/store.js";import{requestSessionIndexPublish as q}from"../session/index-publisher.js";import{RetiredChannelRuntime as D}from"./retired-external-channel-compat.js";import{resolveShennianPath as P}from"../config/index.js";import{buildExternalChannelInstructions as C}from"../agents/external-channel-instructions.js";import{getPersonalSyncIdentity as w}from"../personal-sync/epoch.js";import{EphemeralWorkerError as B,handleEphemeralWorkerIpcRoute as H,handleWorkerIpcRoute as S}from"./worker-ipc.js";import{deriveWorkerTitle as $,formatWorkerMarkdown as U}from"./worker-markdown.js";import{markTrustedManagerEvent as v}from"../session/manager-event-authority.js";const j=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024),y=12*6e4;let b=null;function de(g){b=g}function he(){return b}function f(g){return M.resolve(g||A.homedir())}function m(g,e,t){g.writeHead(e,{"content-type":"application/json; charset=utf-8"}),g.end(JSON.stringify(t))}function R(g){return g.replace(/\r\n/g,`
2
+ `).trim()}function G(g){return g==="worker.final"?"completed":g==="worker.error"?"failed":g==="worker.aborted"?"stopped":"progress"}function F(g,e){return`manager-inbox:${N("sha256").update(g).update("\0").update(e.map(r=>r.id).join("\0")).digest("hex").slice(0,32)}`}async function J(g){const e=[];let t=0;for await(const r of g){const n=Buffer.isBuffer(r)?r:Buffer.from(r);if(t+=n.length,t>j)throw new Error("Request body too large");e.push(n)}return e.length===0?{}:JSON.parse(Buffer.concat(e).toString("utf8"))}function K(g,e,t,r){g.client.sendRes({type:"res",id:e,ok:t,...t?{payload:r}:{error:String(r.error||"unknown error")}})}function x(){return V()?M.join(A.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):P("runtime","manager-ipc.json")}function V(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class le{opts;managedPolicies=new Map;registry=new T;channelRuntime;server=null;ipcUrl=null;ipcToken=W(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;pendingWorkerOrigins=new Map;prepareWorkerTurnOrigin(e,t){t?this.pendingWorkerOrigins.set(e,t):this.pendingWorkerOrigins.delete(e)}constructor(e){this.opts=e,this.channelRuntime=e.channelRuntime??new D}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.registry.clearWorkerRunOrigins(),this.server=_.createServer((t,r)=>{this.handleIpc(t,r)}),this.server.requestTimeout=y,this.server.timeout=y,this.server.keepAliveTimeout=y,this.server.headersTimeout=y+5e3,await new Promise((t,r)=>{this.server.once("error",r),this.server.listen(0,"127.0.0.1",()=>t())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref(),this.recoverPersistedInboxDeliveries()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=x();p.mkdirSync(M.dirname(e),{recursive:!0}),p.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),p.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=x(),t=JSON.parse(p.readFileSync(e,"utf-8"));(t.url===this.ipcUrl||t.token===this.ipcToken)&&p.unlinkSync(e)}catch{}}getInjectedEnv(e,t,r,n){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:t??"",SHENNIAN_MANAGER_WORKDIR:f(r),SHENNIAN_MANAGER_MODEL:n,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:f(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null,managedRoomBound:e.managedRoomBound??(this.managedPolicies.has(e.sessionId)||void 0)})}setManagedAgentPolicy(e,t){if(t){this.managedPolicies.set(e,t);const r=this.registry.getManager(e);r&&!r.managedRoomBound&&this.registry.upsertManager({...r,managedRoomBound:!0})}else this.managedPolicies.delete(e)}getManagedAgentPolicyForWorker(e,t){const r=this.registry.getManager(e),n=this.managedPolicies.get(e);return!r||!n||f(t)!==r.workDir?null:n}isExternalWorkerController(e,t){const r=this.registry.getManager(e);return r?.controllerKind==="ephemeral"&&f(t)===r.workDir}isManagedWorkerSession(e){return!!this.findWorker(e)}isManagedRoomBoundManager(e){return this.registry.getManager(e)?.managedRoomBound===!0}async retireManagerAndWorkers(e){const t=this.registry.retireManager(e);this.managedPolicies.delete(e);const r=this.opts.getRuntime(),n=[],a=async(s,u)=>{const i=[];try{r.runJournal?.markSessionAborted(s)}catch(h){i.push(h)}try{r.recoveryBarrier?.end(s)}catch(h){i.push(h)}try{r.chatQueue?.purgeSession(s)}catch(h){i.push(h)}const o=r.sessions.get(s);let c=!o;if(o){if(o.managedRoomContextToken)try{r.revokeManagedRoomContextToken?.(o.managedRoomContextToken),o.managedRoomContextToken=null}catch(h){i.push(h)}try{await o.adapter.stop(),c=!0}catch(h){i.push(h)}}if(o&&c&&i.length===0&&(r.sessions.delete(s),o.heartbeatTimer&&clearInterval(o.heartbeatTimer),o.adapter.removeAllListeners()),c&&i.length===0)try{u?.()}catch(h){i.push(h),n.push(h)}else n.push(...i)};await a(e);for(const s of t)await a(s.sessionId,()=>this.registry.completeRetiredWorker(e,s.sessionId));if(n.length)throw new AggregateError(n,"Failed to retire every Manager or Worker session");return t.map(s=>s.sessionId)}setManagerWorkerDefaults(e,t,r){const n=this.registry.getManager(e);n&&this.registry.upsertManager({...n,defaultWorkerAgentType:t??null,defaultWorkerModelId:r??null})}getManagerWorkerDefaults(e){const t=this.registry.getManager(e);return{agentType:t?.defaultWorkerAgentType??null,modelId:t?.defaultWorkerModelId??null}}noteManagerAgentSession(e,t,r,n){const a=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:t,workDir:f(r),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:n,agentType:a?.agentType,providerModelId:a?.providerModelId})}noteAgentEvent(e,t){const r=this.findWorker(e);if(!r)return;const n={lastActivityAt:new Date().toISOString(),runId:t.runId};if(t.agentSessionId&&(n.agentSessionId=t.agentSessionId),t.state==="start"){const i=this.pendingWorkerOrigins.get(e);this.pendingWorkerOrigins.delete(e),i&&(n.runRoomOrigins={...r.runRoomOrigins??{},[t.runId]:i})}const a=`${e}:${t.runId}`;let s=t.message||r.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)";if(t.state==="delta"&&t.text&&!t.thinking){const i=(this.workerTextAcc.get(a)??"")+t.text;this.workerTextAcc.set(a,i);const o=R(i);o&&(n.summary=o.length>160?`${o.slice(0,160)}...`:o)}if(t.state==="final"||t.state==="error"||t.state==="aborted"){n.status=t.state;const i=R(this.workerTextAcc.get(a)??"");if(i&&(n.summary=i.length>240?`${i.slice(0,240)}...`:i,s=t.state==="final"?i:t.message||i),this.workerTextAcc.delete(a),r.runRoomOrigins?.[t.runId]){const o={...r.runRoomOrigins};delete o[t.runId],n.runRoomOrigins=o}}else t.state==="start"&&(n.status="running");const u=this.registry.updateWorker(e,n);u&&t.errorCode==="auth_retrying"&&this.registry.enqueueInboxEvent({managerSessionId:u.managedBy,workerSessionId:u.sessionId,kind:"worker.health",priority:"normal",runId:t.runId,roomOrigin:r.runRoomOrigins?.[t.runId]??null,summary:t.message||"Worker authentication is retrying.",code:"auth_retrying"}),u&&(t.state==="final"||t.state==="error"||t.state==="aborted")&&this.enqueueWorkerEvent(u.managedBy,u,t.state,t.runId,s,r.runRoomOrigins?.[t.runId]??null,t.errorCode==="auth_required"||t.errorCode==="acp_auth_required"?"auth_required":void 0)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){K(this.opts.getRuntime(),e.id,!1,{error:"feature_retired"})}async handleIpc(e,t){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){m(t,401,{ok:!1,error:"Unauthorized"});return}try{const r=new URL(e.url??"/","http://127.0.0.1"),n=await J(e);if(r.pathname.startsWith("/channel/")||r.pathname.startsWith("/external/")||r.pathname.startsWith("/wechat-rpa/")){m(t,410,{ok:!1,error:"feature_retired"});return}const a={registry:this.registry,getRuntime:this.opts.getRuntime,getAgentSnapshot:this.opts.getAgentSnapshot,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},s=await H(a,r.pathname,n)??await S(a,r.pathname,n,String(e.headers["x-shennian-manager-session-id"]||""));if(s){m(t,s.status,s.body);return}m(t,404,{ok:!1,error:`Unknown manager IPC path: ${r.pathname}`})}catch(r){if(r instanceof B){m(t,r.status,{ok:!1,error:r.code,message:r.message,nextAction:r.nextAction});return}m(t,400,{ok:!1,error:r instanceof Error?r.message:String(r)})}}async executeScopedWorkerAction(e,t,r,n){const a={"manager.sessions.list":"/sessions/list","manager.sessions.start":"/sessions/start","manager.sessions.send":"/sessions/send","manager.sessions.queue":"/sessions/queue","manager.sessions.queue.edit":"/sessions/queue/edit","manager.sessions.queue.delete":"/sessions/queue/delete","manager.sessions.stop":"/sessions/stop","manager.sessions.read":"/sessions/read","manager.memory.path":"/memory/path"},s=await S({registry:this.registry,getRuntime:this.opts.getRuntime,getAgentSnapshot:this.opts.getAgentSnapshot,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},a[t],r,e,{roomOrigin:n});if(!s||s.status<200||s.status>=300)throw new Error(String(s?.body.error||"Managed Manager action failed"));return JSON.parse(JSON.stringify(s.body))}async dispatchChatEnqueue(e,t,r,n,a,s,u,i){if((await this.opts.dispatchReq(v({type:"req",id:`manager-enqueue-${k()}`,method:"chat.enqueue",params:{...w(),sessionId:e,text:n,agentType:t,workDir:r,agentSessionId:a,modelId:s,managedWorkerParentSessionId:u,managerWorkerRoomOrigin:i}})))?.durableQueueHandoff===!1)throw new Error("manager_worker_queue_handoff_unconfirmed")}appendManagerVisibleMarkdown(e,t,r){this.registry.getManager(e)?.controllerKind==="managed_session"&&(O(e,{id:r,sessionId:e,role:"agent",ts:Date.now(),payload:t},"committed"),q(this.opts.getRuntime().client))}enqueueWorkerEvent(e,t,r,n,a,s,u){const i=this.registry.enqueueInboxEvent({managerSessionId:e,workerSessionId:t.sessionId,kind:r==="final"?"worker.final":r==="error"?"worker.error":"worker.aborted",priority:"normal",runId:n??null,roomOrigin:s??null,summary:a||t.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)",...u?{code:u}:{}}),o=this.registry.getManager(e);i&&o?.controllerKind==="managed_session"&&this.drainManagerInbox(e)}scanWorkerHealth(){const e=Date.now(),t=this.registry.load(),r=this.opts.getRuntime();for(const n of Object.values(t.workers)){if(n.status!=="running")continue;const a=e-Date.parse(n.createdAt);if(a<10*6e4)continue;const s=e-Date.parse(n.lastActivityAt),u=r.sessions.get(n.sessionId)?.currentRunId,i=!!r.chatQueue?.getSnapshot(n.sessionId).pending.length;if(!u){if(!i&&s>=10*6e4){const E="\u672A\u627E\u5230\u6D3B\u52A8\u4E2D\u7684 Provider Run \u6216\u5F85\u5904\u7406\u6D88\u606F\uFF1B\u5DF2\u5C06\u9057\u7559\u7684\u8FD0\u884C\u72B6\u6001\u6536\u655B\u4E3A\u5931\u8D25\u3002\u539F Worker \u4F1A\u8BDD\u4ECD\u4FDD\u7559\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002",I=this.registry.updateWorker(n.sessionId,{status:"error",lastActivityAt:new Date(e).toISOString(),summary:E});I&&this.enqueueWorkerEvent(I.managedBy,I,"error",n.runId??void 0,E)}continue}const o=n.healthNotifiedAt?Date.parse(n.healthNotifiedAt):0;if(o&&e-o<10*6e4)continue;const c=t.managers[n.managedBy];if(!c)continue;const h=Math.max(0,Math.floor(s/6e4)),d=R(n.runId?this.workerTextAcc.get(`${n.sessionId}:${n.runId}`)??"":""),l=`\u5DF2\u8FD0\u884C ${Math.floor(a/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
3
3
 
4
4
  \u5F53\u524D\u53EF\u89C1\u8FDB\u5C55\uFF1A
5
- - \u6700\u8FD1\u6587\u672C\uFF1A${g||n.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)"}
5
+ - \u6700\u8FD1\u6587\u672C\uFF1A${d||n.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)"}
6
6
  - \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${n.lastActivityAt}
7
- - \u6700\u8FD1 ${h} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:l}),this.registry.enqueueInboxEvent({managerSessionId:c.sessionId,workerSessionId:n.sessionId,kind:"worker.health",priority:"normal",runId:n.runId??null,roomOrigin:n.runRoomOrigins?.[n.runId??""]??null,summary:l})&&c.controllerKind==="managed_session"&&this.drainManagerInbox(c.sessionId)}}async interruptAndResumeManager(e,r,t,n){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${k()}`,method:"chat.abort",params:{...w(),sessionId:e.sessionId}}),await this.dispatchManagerContinuation(e,r,!1,t,n)}bindManagerAdapterEvents(e,r){r.on("agentEvent",t=>{if(t.state==="start"&&t.agentSessionId){const n=this.registry.getManager(e);n&&this.noteManagerAgentSession(e,t.agentSessionId,n.workDir,n.modelId)}t.state==="start"&&(this.acknowledgeManagerInboxDelivery(e),this.updateManagerStatus(e,"running")),(t.state==="final"||t.state==="error"||t.state==="aborted")&&(this.updateManagerStatus(e,"idle"),queueMicrotask(()=>{this.drainManagerInbox(e)}))})}updateManagerStatus(e,r){const t=this.registry.getManager(e);t&&this.registry.upsertManager({...t,status:r})}async drainManagerInbox(e){const r=this.registry.getManager(e);if(!r||r.deliveringInboxEventIds?.length)return;const t=this.registry.listInboxEvents(e);if(!t.length)return;const n=t.find(g=>g.priority==="high"),a=this.opts.getRuntime().sessions.get(e);if(r.status!=="idle"&&!n||a?.currentRunId&&!n)return;const u=(n??t[0]).roomOrigin??null,i=JSON.stringify(u),o=n?[n]:t.filter(g=>JSON.stringify(g.roomOrigin??null)===i).slice(0,20),c=o.map(g=>{const l=this.registry.getWorkerForManager(e,g.workerSessionId);return U({kind:G(g.kind),sessionId:g.workerSessionId,title:l?.title||$(l?.userGoal||l?.summary||g.summary),body:g.summary})}).join(`
7
+ - \u6700\u8FD1 ${h} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:l}),this.registry.enqueueInboxEvent({managerSessionId:c.sessionId,workerSessionId:n.sessionId,kind:"worker.health",priority:"normal",runId:n.runId??null,roomOrigin:n.runRoomOrigins?.[n.runId??""]??null,summary:l})&&c.controllerKind==="managed_session"&&this.drainManagerInbox(c.sessionId)}}async interruptAndResumeManager(e,t,r,n){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${k()}`,method:"chat.abort",params:{...w(),sessionId:e.sessionId}}),await this.dispatchManagerContinuation(e,t,!1,r,n)}bindManagerAdapterEvents(e,t){t.on("agentEvent",r=>{if(r.state==="start"&&r.agentSessionId){const n=this.registry.getManager(e);n&&this.noteManagerAgentSession(e,r.agentSessionId,n.workDir,n.modelId)}r.state==="start"&&(this.acknowledgeManagerInboxDelivery(e),this.updateManagerStatus(e,"running")),(r.state==="final"||r.state==="error"||r.state==="aborted")&&(this.updateManagerStatus(e,"idle"),queueMicrotask(()=>{this.drainManagerInbox(e)}))})}updateManagerStatus(e,t){const r=this.registry.getManager(e);r&&this.registry.upsertManager({...r,status:t})}async drainManagerInbox(e){const t=this.registry.getManager(e);if(!t||t.deliveringInboxEventIds?.length)return;const r=this.registry.listInboxEvents(e);if(!r.length)return;const n=r.find(d=>d.priority==="high"),a=this.opts.getRuntime().sessions.get(e);if(t.status!=="idle"&&!n||a?.currentRunId&&!n)return;const u=(n??r[0]).roomOrigin??null,i=JSON.stringify(u),o=n?[n]:r.filter(d=>JSON.stringify(d.roomOrigin??null)===i).slice(0,20),c=o.map(d=>{const l=this.registry.getWorkerForManager(e,d.workerSessionId);return U({kind:G(d.kind),sessionId:d.workerSessionId,title:l?.title||$(l?.userGoal||l?.summary||d.summary),body:d.summary})}).join(`
8
8
 
9
9
  ---
10
10
 
11
- `),h=F(e,o);try{n&&a?.currentRunId?(this.registry.upsertManager({...r,status:"interrupting",deliveringInboxEventIds:o.map(l=>l.id)}),await this.interruptAndResumeManager(r,c,h,u)):(this.registry.upsertManager({...r,status:"running",deliveringInboxEventIds:o.map(l=>l.id)}),await this.dispatchManagerContinuation(r,c,!0,h,u)),this.registry.removeInboxEvents(e,o.map(l=>l.id));const g=this.registry.getManager(e);g&&this.registry.upsertManager({...g,deliveringInboxEventIds:[]})}catch(g){const l=this.registry.getManager(e);l&&this.registry.upsertManager({...l,status:"idle",deliveringInboxEventIds:[]}),console.error(`[manager-inbox] delivery failed sessionId=${e}: ${g instanceof Error?g.message:String(g)}`)}}acknowledgeManagerInboxDelivery(e){const r=this.registry.getManager(e),t=r?.deliveringInboxEventIds??[];!r||t.length===0||(this.registry.removeInboxEvents(e,t),this.registry.upsertManager({...r,deliveringInboxEventIds:[]}))}recoverPersistedInboxDeliveries(){const e=this.registry.load(),r=this.opts.getRuntime();for(const t of Object.values(e.managers)){if(r.sessions.get(t.sessionId)?.currentRunId)continue;(t.status!=="idle"||t.deliveringInboxEventIds?.length)&&this.registry.upsertManager({...t,status:"idle",deliveringInboxEventIds:[]});const a=this.registry.listInboxEvents(t.sessionId).filter(s=>s.kind==="worker.health"&&!r.sessions.get(s.workerSessionId)?.currentRunId).map(s=>s.id);this.registry.removeInboxEvents(t.sessionId,a),this.registry.listInboxEvents(t.sessionId).length>0&&queueMicrotask(()=>{this.drainManagerInbox(t.sessionId)})}}async dispatchManagerContinuation(e,r,t=!1,n,a){const s=e.agentType??"manager",u=s==="manager"?e.modelId:e.providerModelId??void 0,i={...w(),sessionId:e.sessionId,text:r,agentType:s,sessionMode:"manager",workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:u,managerDefaultWorkerAgentType:e.defaultWorkerAgentType??null,managerDefaultWorkerModelId:e.defaultWorkerModelId??null,...e.managedRoomBound?{managedAgentDirect:!0}:{},clientMessageId:n,managerEventId:n,...a?{managerEventRoomOrigin:a}:{}};if((await this.opts.dispatchReq(S({type:"req",id:`manager-${t?"enqueue":"send"}-${k()}`,method:"chat.enqueue",params:i})))?.durableQueueHandoff===!1)throw new Error("manager_event_queue_handoff_unconfirmed")}getExternalChannelStatus(e){return this.channelRuntime.getManagerChannelStatus(e)}getManagerExternalChannelSystemPrompt(e){return this.channelRuntime.listManagerExternalChannels(e).map(r=>C(r,void 0,e,"manager")).join(`
11
+ `),h=F(e,o);try{n&&a?.currentRunId?(this.registry.upsertManager({...t,status:"interrupting",deliveringInboxEventIds:o.map(l=>l.id)}),await this.interruptAndResumeManager(t,c,h,u)):(this.registry.upsertManager({...t,status:"running",deliveringInboxEventIds:o.map(l=>l.id)}),await this.dispatchManagerContinuation(t,c,!0,h,u)),this.registry.removeInboxEvents(e,o.map(l=>l.id));const d=this.registry.getManager(e);d&&this.registry.upsertManager({...d,deliveringInboxEventIds:[]})}catch(d){const l=this.registry.getManager(e);l&&this.registry.upsertManager({...l,status:"idle",deliveringInboxEventIds:[]}),console.error(`[manager-inbox] delivery failed sessionId=${e}: ${d instanceof Error?d.message:String(d)}`)}}acknowledgeManagerInboxDelivery(e){const t=this.registry.getManager(e),r=t?.deliveringInboxEventIds??[];!t||r.length===0||(this.registry.removeInboxEvents(e,r),this.registry.upsertManager({...t,deliveringInboxEventIds:[]}))}recoverPersistedInboxDeliveries(){const e=this.registry.load(),t=this.opts.getRuntime();for(const r of Object.values(e.managers)){if(t.sessions.get(r.sessionId)?.currentRunId)continue;(r.status!=="idle"||r.deliveringInboxEventIds?.length)&&this.registry.upsertManager({...r,status:"idle",deliveringInboxEventIds:[]});const a=this.registry.listInboxEvents(r.sessionId).filter(s=>s.kind==="worker.health"&&!t.sessions.get(s.workerSessionId)?.currentRunId).map(s=>s.id);this.registry.removeInboxEvents(r.sessionId,a),this.registry.listInboxEvents(r.sessionId).length>0&&queueMicrotask(()=>{this.drainManagerInbox(r.sessionId)})}}async dispatchManagerContinuation(e,t,r=!1,n,a){const s=e.agentType??"manager",u=s==="manager"?e.modelId:e.providerModelId??void 0,i={...w(),sessionId:e.sessionId,text:t,agentType:s,sessionMode:"manager",workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:u,managerDefaultWorkerAgentType:e.defaultWorkerAgentType??null,managerDefaultWorkerModelId:e.defaultWorkerModelId??null,...e.managedRoomBound?{managedAgentDirect:!0}:{},clientMessageId:n,managerEventId:n,...a?{managerEventRoomOrigin:a}:{}};if((await this.opts.dispatchReq(v({type:"req",id:`manager-${r?"enqueue":"send"}-${k()}`,method:"chat.enqueue",params:i})))?.durableQueueHandoff===!1)throw new Error("manager_event_queue_handoff_unconfirmed")}getExternalChannelStatus(e){return this.channelRuntime.getManagerChannelStatus(e)}getManagerExternalChannelSystemPrompt(e){return this.channelRuntime.listManagerExternalChannels(e).map(t=>C(t,void 0,e,"manager")).join(`
12
12
 
13
- `).trim()}}export{le as ManagerRuntimeService,he as getManagerRuntimeService,ge as setManagerRuntimeService};
13
+ `).trim()}}export{le as ManagerRuntimeService,he as getManagerRuntimeService,de as setManagerRuntimeService};
@@ -1,4 +1,4 @@
1
- import { type AgentType } from '@shennian/wire';
1
+ import { type AgentInfo, type AgentType } from '@shennian/wire';
2
2
  import type { SessionManagerRuntime } from '../session/types.js';
3
3
  import { ManagerRegistry, type ManagedWorkerRoomOrigin } from './registry.js';
4
4
  import type { LocalReqDispatcher } from './runtime.js';
@@ -9,6 +9,7 @@ export type WorkerIpcResponse = {
9
9
  type WorkerIpcHost = {
10
10
  registry: ManagerRegistry;
11
11
  getRuntime: () => SessionManagerRuntime;
12
+ getAgentSnapshot?: () => AgentInfo[];
12
13
  dispatchReq: LocalReqDispatcher;
13
14
  dispatchChatEnqueue: (sessionId: string, agentType: AgentType, workDir: string, text: string, agentSessionId: string | null, modelId?: string, managedWorkerParentSessionId?: string, roomOrigin?: ManagedWorkerRoomOrigin) => Promise<void>;
14
15
  appendManagerVisibleMarkdown: (managerSessionId: string, markdown: string, id: string) => void;
@@ -1 +1 @@
1
- import S from"node:fs";import q from"node:path";import{randomUUID as W}from"node:crypto";import{extractPayloadText as $,isAvailableAgentType as P,isAgentHiddenPayload as C,isToolPayload as U}from"@shennian/wire";import{loadConfig as O}from"../config/index.js";import{detectAgents as j}from"../agents/detect.js";import{getCachedAgentInfos as N}from"../agents/model-registry/service.js";import{getPersonalSyncIdentity as T}from"../personal-sync/epoch.js";import{readMessages as G,recordSession as K}from"../session/store.js";import{deriveWorkerTitle as p,formatWorkerMarkdown as h,normalizeWorkerTitle as L}from"./worker-markdown.js";class g extends Error{code;nextAction;status;constructor(a,r,i,k=400){super(r),this.code=a,this.nextAction=i,this.status=k}}function b(e){return q.resolve(e||process.cwd())}function B(e){const a=b(e);let r;try{r=S.statSync(a)}catch{throw new g("worker_workdir_invalid","The Worker working directory does not exist.","Choose an existing local project directory and retry worker start.")}if(!r.isDirectory())throw new g("worker_workdir_invalid","The Worker working directory is not a directory.","Choose an existing local project directory and retry worker start.");const i=S.realpathSync.native(a);try{S.accessSync(i,S.constants.R_OK|S.constants.X_OK)}catch{throw new g("worker_workdir_inaccessible","The Worker working directory is not readable and searchable by this user.","Choose a project directory this Shennian daemon user can access and retry worker start.")}return i}function x(e){return P(e)&&e!=="manager"&&e!=="pi"&&e!=="openclaw"}function z(e){return e==="worker.final"?"completed":e==="worker.error"?"failed":e==="worker.aborted"?"stopped":"progress"}function F(e,a){const r=[];for(const i of e)if(!C(i.payload))if(U(i.payload)){let k="tool",c="completed";try{const d=JSON.parse(i.payload);k=d.name||k,c=d.status||c}catch{}r.push({...i,payload:`[\u5DE5\u5177] ${k} \xB7 ${c}`})}else r.push({...i,payload:$(i.payload)});return r.slice(-Math.max(1,Math.min(a,500)))}async function M(e,a,r,i,k){if(!a.startsWith("/sessions/")&&a!=="/memory/path")return null;const c=String(r.managerSessionId||i||"");if(!c)throw new Error("managerSessionId is required");const d=e.registry.getManager(c);if(!d)throw new Error("Manager runtime is not registered");if(a==="/sessions/list"){const s=new Set([...e.getRuntime().sessions.entries()].filter(([,u])=>!!u.currentRunId).map(([u])=>u));return{status:200,body:{ok:!0,sessions:e.registry.listWorkers(c,{runningSessionIds:s})}}}if(a==="/memory/path")return{status:200,body:{ok:!0,path:q.join(d.workDir,".shennian")}};if(a==="/sessions/start"){const s=d.controllerKind==="ephemeral",o=s?String(r.agentType||r.agent||""):d.defaultWorkerAgentType||d.agentType||d.modelId;if(!x(o))throw new Error(`Unsupported manager worker agent: ${o}`);const u=s?b(String(r.workDir||d.workDir)):d.workDir;if(u!==d.workDir)throw new Error("Manager can only start workers in the same workDir");const m=String(r.message||"");if(!m)throw new Error("message is required");const n=r.title?L(String(r.title)):p(m),w=e.registry.addWorker({managerSessionId:c,agentType:o,workDir:u,title:n,summary:m.slice(0,120)}),f=s?String(r.modelId||""):d.defaultWorkerModelId??d.providerModelId??"";K({sessionId:w.sessionId,agentType:o,workDir:u,agentSessionId:null,modelId:f||null,title:n,titleSource:"shennian_generated"}),await e.dispatchChatEnqueue(w.sessionId,o,u,m,null,f||void 0,c,k?.roomOrigin);const y=h({kind:"created",sessionId:w.sessionId,title:n,body:m});return e.appendManagerVisibleMarkdown(c,y,`worker-created:${w.sessionId}`),{status:200,body:{ok:!0,session:w,markdown:y}}}const l=String(r.sessionId||""),t=e.registry.getWorkerForManager(c,l);if(!t||t.role!=="managed_worker"||t.managedBy!==c)throw new Error("Worker not found in this manager scope");if(a==="/sessions/send"){const s=String(r.message||"");if(!s.trim())throw new Error("message is required");const o=r.enqueue===void 0?!0:!!r.enqueue,m=e.getRuntime().sessions.get(l)?.currentRunId?"worker_busy":t.status==="final"||t.status==="error"||t.status==="aborted"?"worker_terminal":void 0,n=String(r.modelId||(t.agentType===d.defaultWorkerAgentType?d.defaultWorkerModelId??"":""));await e.dispatchChatEnqueue(t.sessionId,t.agentType,t.workDir,s,t.agentSessionId??null,n||void 0,c,k?.roomOrigin);const w=h({kind:"message",sessionId:l,title:t.title||p(t.userGoal||t.summary||s),body:s});return e.appendManagerVisibleMarkdown(c,w,`worker-message:${W()}`),{status:200,body:{ok:!0,markdown:w,queued:!0,directRequested:!o,...m?{code:m}:{}}}}if(a==="/sessions/queue")return{status:200,body:{ok:!0,queue:e.getRuntime().chatQueue?.getSnapshot(l)}};if(a==="/sessions/queue/edit"||a==="/sessions/queue/delete"){const s=a.endsWith("/edit");return await e.dispatchReq({type:"req",id:`manager-queue-${s?"edit":"delete"}-${W()}`,method:s?"chat.queue.edit":"chat.queue.delete",params:{...T(),sessionId:l,queueMessageId:String(r.queueMessageId||r.messageId||""),...s?{text:String(r.message||r.text||"")}:{}}}),{status:200,body:{ok:!0,queue:e.getRuntime().chatQueue?.getSnapshot(l)}}}if(a==="/sessions/stop"||a==="/sessions/terminate"){if(t.status==="final"||t.status==="error"||t.status==="aborted"){const s=t.status==="final"?"completed":t.status==="error"?"failed":"stopped";return{status:200,body:{ok:!0,alreadyTerminal:!0,code:"worker_terminal",status:t.status,markdown:h({kind:s,sessionId:l,title:t.title||p(t.userGoal||t.summary||""),body:t.summary||"\u672C\u6B21\u6267\u884C\u5DF2\u7ECF\u7ED3\u675F\u3002"})}}}return await e.dispatchReq({type:"req",id:`manager-abort-${W()}`,method:"chat.abort",params:{...T(),sessionId:l}}),e.registry.updateWorker(l,{status:"aborted"}),{status:200,body:{ok:!0,markdown:h({kind:"stopped",sessionId:l,title:t.title||p(t.userGoal||t.summary||""),body:"\u5DF2\u505C\u6B62\u672C\u6B21\u6267\u884C\u3002"})}}}if(a==="/sessions/read"){const s=Number(r.limit||200),o=G(l,{limit:Math.max(s*20,s)});return{status:200,body:{ok:!0,messages:F(o,s),rawMessageCount:o.length}}}if(a==="/sessions/wait"){const s=Math.max(1e3,Math.min(Number(r.timeoutMs||6e5),6e5)),o=Date.now()+s;let u;for(;Date.now()<o&&(u=e.registry.listInboxEvents(c).find(n=>n.workerSessionId===l),!u);)await new Promise(n=>setTimeout(n,1e3));if(!u){const n=e.registry.getWorkerForManager(c,l);if(n&&(n.status==="final"||n.status==="error"||n.status==="aborted")){const w=n.status==="final"?"completed":n.status==="error"?"failed":"stopped";return{status:200,body:{ok:!0,timedOut:!1,alreadyTerminal:!0,status:n.status,markdown:h({kind:w,sessionId:l,title:n.title||p(n.userGoal||n.summary||""),body:n.summary||"\u672C\u6B21\u6267\u884C\u5DF2\u7ECF\u7ED3\u675F\u3002"})}}}return{status:200,body:{ok:!0,timedOut:!0,code:"wait_timed_out",status:n?.status??"running",nextAction:"Call worker read or worker wait again later with the same workerRef."}}}const m=h({kind:z(u.kind),sessionId:l,title:t.title||p(t.userGoal||t.summary||u.summary),body:u.summary});return e.registry.removeInboxEvents(c,[u.id]),{status:200,body:{ok:!0,timedOut:!1,markdown:m,event:u}}}return null}const H={"/workers/read":{action:"read",managerPath:"/sessions/read"},"/workers/send":{action:"send",managerPath:"/sessions/send"},"/workers/wait":{action:"wait",managerPath:"/sessions/wait"},"/workers/stop":{action:"stop",managerPath:"/sessions/stop"}};async function oe(e,a,r){if(!a.startsWith("/workers/"))return null;const i=O();if(!i.machineId||!i.machineToken&&!i.accessToken)throw new g("auth_required","Shennian CLI must be paired and logged in.","Pair this installation with shennian pair, then retry the same worker command.",401);if(a==="/workers/start"){const s=String(r.message||"").trim();if(!s)throw new g("worker_message_required","A Worker task message is required.","Provide --message or --message-file and retry worker start.");const o=String(r.agentType||r.agent||"");if(!o||!x(o))throw new g("runtime_unavailable",`Unsupported Worker Agent: ${o||"(empty)"}`,"Run shennian agent discover --compact --json and select a Worker-capable Agent type.");const u=await j({serverUrl:i.serverUrl,authToken:i.machineToken??i.accessToken}),m=N(u).find(I=>I.type===o);if(!m||m.supportsExternalWorkerTarget!==!0)throw new g("runtime_unavailable",`Worker Agent is not available on this machine: ${o}`,"Run shennian agent discover --compact --json and select a discovered Agent with supportsExternalWorkerTarget=true.");if(m.authState==="required"||m.authState==="error")throw new g("auth_required",`Worker Agent requires provider sign-in: ${o}`,"Sign in with the provider\u2019s own CLI on this computer, refresh discovery, and retry worker start.");const n=typeof r.modelId=="string"?r.modelId.trim():"";if(n&&m.models.length>0&&!m.models.some(I=>I.id===n))throw new g("model_unavailable",`The selected model was not returned by discovery for ${o}.`,`Run shennian agent discover --agent ${o} --json and choose a returned model id.`);const w=B(String(r.workDir||process.cwd())),f=`ephemeral_controller_${W()}`;e.registry.upsertManager({sessionId:f,agentSessionId:null,workDir:w,machineId:i.machineId,modelId:"manager",agentType:"manager",controllerKind:"ephemeral",status:"idle"});let y;try{y=await M(e,"/sessions/start",{managerSessionId:f,agentType:o,workDir:w,title:r.title,modelId:n||void 0,message:s},"")}catch(I){const A=e.registry.retireManager(f);for(const D of A)e.registry.completeRetiredWorker(f,D.sessionId);throw I}const _=y.body.session,v=typeof _?.sessionId=="string"?_.sessionId:"";if(!v)throw new Error("worker_start_invalid_response");const{workerRef:R,grant:E}=e.registry.issueWorkerRef({managerSessionId:f,workerSessionId:v,workDir:w,machineId:i.machineId,accountNamespaceId:i.accountNamespaceId??null});return{status:200,body:{ok:!0,workerId:v,workerRef:R,resolvedWorkDir:w,agentType:o,status:typeof _?.status=="string"?_.status:"running",expiresAt:E.expiresAt}}}const k=H[a];if(!k)throw new g("worker_command_unavailable","Unknown external Worker command.","Use worker start, read, send, wait, or stop. External Worker history cannot be listed.",404);const c=String(r.workerRef||r.ref||""),d=e.registry.resolveWorkerRefDetailed({workerRef:c,action:k.action,machineId:i.machineId,accountNamespaceId:i.accountNamespaceId??null});if(!d.ok){const s=d.reason==="expired"?"worker_ref_expired":d.reason==="revoked"?"worker_ref_revoked":"worker_ref_invalid";throw new g(s,`The Worker reference is ${d.reason} or belongs to another local scope.`,d.reason==="invalid"?"Use the exact workerRef returned by worker start. Lost references cannot be rediscovered.":"Start a new Worker if the task still needs to continue.",403)}const l=d.grant;let t;try{t=await M(e,k.managerPath,{...r,managerSessionId:l.managerSessionId,sessionId:l.workerSessionId},"")}catch(s){const o=s instanceof Error?s.message:String(s);throw o==="message is required"?new g("worker_message_required","A non-empty Worker message is required.","Provide --message or --message-file and retry worker send."):o.includes("not found")||o.includes("not registered")?new g("worker_ref_revoked","The Worker is no longer available in this controller scope.","Start a new Worker if the task still needs to continue.",410):new g("worker_command_failed",o,"Read the Worker state, correct the request, and retry once.")}if(!t)throw new g("worker_command_unavailable","The requested Worker operation is unavailable.","Use worker start, read, send, wait, or stop.",404);return{status:t.status,body:{...t.body,workerId:l.workerSessionId}}}export{g as EphemeralWorkerError,oe as handleEphemeralWorkerIpcRoute,M as handleWorkerIpcRoute};
1
+ import S from"node:fs";import v from"node:path";import{randomUUID as _}from"node:crypto";import{extractPayloadText as D,isAvailableAgentType as $,isAgentHiddenPayload as P,isToolPayload as C}from"@shennian/wire";import{loadConfig as U}from"../config/index.js";import{getPersonalSyncIdentity as q}from"../personal-sync/epoch.js";import{readMessages as O,recordSession as j}from"../session/store.js";import{deriveWorkerTitle as p,formatWorkerMarkdown as h,normalizeWorkerTitle as N}from"./worker-markdown.js";class w extends Error{code;nextAction;status;constructor(a,r,u,k=400){super(r),this.code=a,this.nextAction=u,this.status=k}}function b(e){return v.resolve(e||process.cwd())}function G(e){const a=b(e);let r;try{r=S.statSync(a)}catch{throw new w("worker_workdir_invalid","The Worker working directory does not exist.","Choose an existing local project directory and retry worker start.")}if(!r.isDirectory())throw new w("worker_workdir_invalid","The Worker working directory is not a directory.","Choose an existing local project directory and retry worker start.");const u=S.realpathSync.native(a);try{S.accessSync(u,S.constants.R_OK|S.constants.X_OK)}catch{throw new w("worker_workdir_inaccessible","The Worker working directory is not readable and searchable by this user.","Choose a project directory this Shennian daemon user can access and retry worker start.")}return u}function x(e){return $(e)&&e!=="manager"&&e!=="pi"&&e!=="openclaw"}function K(e){return e==="worker.final"?"completed":e==="worker.error"?"failed":e==="worker.aborted"?"stopped":"progress"}function L(e,a){const r=[];for(const u of e)if(!P(u.payload))if(C(u.payload)){let k="tool",c="completed";try{const d=JSON.parse(u.payload);k=d.name||k,c=d.status||c}catch{}r.push({...u,payload:`[\u5DE5\u5177] ${k} \xB7 ${c}`})}else r.push({...u,payload:D(u.payload)});return r.slice(-Math.max(1,Math.min(a,500)))}async function M(e,a,r,u,k){if(!a.startsWith("/sessions/")&&a!=="/memory/path")return null;const c=String(r.managerSessionId||u||"");if(!c)throw new Error("managerSessionId is required");const d=e.registry.getManager(c);if(!d)throw new Error("Manager runtime is not registered");if(a==="/sessions/list"){const s=new Set([...e.getRuntime().sessions.entries()].filter(([,i])=>!!i.currentRunId).map(([i])=>i));return{status:200,body:{ok:!0,sessions:e.registry.listWorkers(c,{runningSessionIds:s})}}}if(a==="/memory/path")return{status:200,body:{ok:!0,path:v.join(d.workDir,".shennian")}};if(a==="/sessions/start"){const s=d.controllerKind==="ephemeral",o=s?String(r.agentType||r.agent||""):d.defaultWorkerAgentType||d.agentType||d.modelId;if(!x(o))throw new Error(`Unsupported manager worker agent: ${o}`);const i=s?b(String(r.workDir||d.workDir)):d.workDir;if(i!==d.workDir)throw new Error("Manager can only start workers in the same workDir");const g=String(r.message||"");if(!g)throw new Error("message is required");const n=r.title?N(String(r.title)):p(g),m=e.registry.addWorker({managerSessionId:c,agentType:o,workDir:i,title:n,summary:g.slice(0,120)}),y=s?String(r.modelId||""):d.defaultWorkerModelId??d.providerModelId??"";j({sessionId:m.sessionId,agentType:o,workDir:i,agentSessionId:null,modelId:y||null,title:n,titleSource:"shennian_generated"}),await e.dispatchChatEnqueue(m.sessionId,o,i,g,null,y||void 0,c,k?.roomOrigin);const f=h({kind:"created",sessionId:m.sessionId,title:n,body:g});return e.appendManagerVisibleMarkdown(c,f,`worker-created:${m.sessionId}`),{status:200,body:{ok:!0,session:m,markdown:f}}}const l=String(r.sessionId||""),t=e.registry.getWorkerForManager(c,l);if(!t||t.role!=="managed_worker"||t.managedBy!==c)throw new Error("Worker not found in this manager scope");if(a==="/sessions/send"){const s=String(r.message||"");if(!s.trim())throw new Error("message is required");const o=r.enqueue===void 0?!0:!!r.enqueue,g=e.getRuntime().sessions.get(l)?.currentRunId?"worker_busy":t.status==="final"||t.status==="error"||t.status==="aborted"?"worker_terminal":void 0,n=String(r.modelId||(t.agentType===d.defaultWorkerAgentType?d.defaultWorkerModelId??"":""));await e.dispatchChatEnqueue(t.sessionId,t.agentType,t.workDir,s,t.agentSessionId??null,n||void 0,c,k?.roomOrigin);const m=h({kind:"message",sessionId:l,title:t.title||p(t.userGoal||t.summary||s),body:s});return e.appendManagerVisibleMarkdown(c,m,`worker-message:${_()}`),{status:200,body:{ok:!0,markdown:m,queued:!0,directRequested:!o,...g?{code:g}:{}}}}if(a==="/sessions/queue")return{status:200,body:{ok:!0,queue:e.getRuntime().chatQueue?.getSnapshot(l)}};if(a==="/sessions/queue/edit"||a==="/sessions/queue/delete"){const s=a.endsWith("/edit");return await e.dispatchReq({type:"req",id:`manager-queue-${s?"edit":"delete"}-${_()}`,method:s?"chat.queue.edit":"chat.queue.delete",params:{...q(),sessionId:l,queueMessageId:String(r.queueMessageId||r.messageId||""),...s?{text:String(r.message||r.text||"")}:{}}}),{status:200,body:{ok:!0,queue:e.getRuntime().chatQueue?.getSnapshot(l)}}}if(a==="/sessions/stop"||a==="/sessions/terminate"){if(t.status==="final"||t.status==="error"||t.status==="aborted"){const s=t.status==="final"?"completed":t.status==="error"?"failed":"stopped";return{status:200,body:{ok:!0,alreadyTerminal:!0,code:"worker_terminal",status:t.status,markdown:h({kind:s,sessionId:l,title:t.title||p(t.userGoal||t.summary||""),body:t.summary||"\u672C\u6B21\u6267\u884C\u5DF2\u7ECF\u7ED3\u675F\u3002"})}}}return await e.dispatchReq({type:"req",id:`manager-abort-${_()}`,method:"chat.abort",params:{...q(),sessionId:l}}),e.registry.updateWorker(l,{status:"aborted"}),{status:200,body:{ok:!0,markdown:h({kind:"stopped",sessionId:l,title:t.title||p(t.userGoal||t.summary||""),body:"\u5DF2\u505C\u6B62\u672C\u6B21\u6267\u884C\u3002"})}}}if(a==="/sessions/read"){const s=Number(r.limit||200),o=O(l,{limit:Math.max(s*20,s)});return{status:200,body:{ok:!0,messages:L(o,s),rawMessageCount:o.length}}}if(a==="/sessions/wait"){const s=Math.max(1e3,Math.min(Number(r.timeoutMs||6e5),6e5)),o=Date.now()+s;let i;for(;Date.now()<o&&(i=e.registry.listInboxEvents(c).find(n=>n.workerSessionId===l),!i);)await new Promise(n=>setTimeout(n,1e3));if(!i){const n=e.registry.getWorkerForManager(c,l);if(n&&(n.status==="final"||n.status==="error"||n.status==="aborted")){const m=n.status==="final"?"completed":n.status==="error"?"failed":"stopped";return{status:200,body:{ok:!0,timedOut:!1,alreadyTerminal:!0,status:n.status,markdown:h({kind:m,sessionId:l,title:n.title||p(n.userGoal||n.summary||""),body:n.summary||"\u672C\u6B21\u6267\u884C\u5DF2\u7ECF\u7ED3\u675F\u3002"})}}}return{status:200,body:{ok:!0,timedOut:!0,code:"wait_timed_out",status:n?.status??"running",nextAction:"Call worker read or worker wait again later with the same workerRef."}}}const g=h({kind:K(i.kind),sessionId:l,title:t.title||p(t.userGoal||t.summary||i.summary),body:i.summary});return e.registry.removeInboxEvents(c,[i.id]),{status:200,body:{ok:!0,timedOut:!1,markdown:g,event:i}}}return null}const B={"/workers/read":{action:"read",managerPath:"/sessions/read"},"/workers/send":{action:"send",managerPath:"/sessions/send"},"/workers/wait":{action:"wait",managerPath:"/sessions/wait"},"/workers/stop":{action:"stop",managerPath:"/sessions/stop"}};async function Z(e,a,r){if(!a.startsWith("/workers/"))return null;const u=U();if(!u.machineId||!u.machineToken&&!u.accessToken)throw new w("auth_required","Shennian CLI must be paired and logged in.","Pair this installation with shennian pair, then retry the same worker command.",401);if(a==="/workers/start"){const s=String(r.message||"").trim();if(!s)throw new w("worker_message_required","A Worker task message is required.","Provide --message or --message-file and retry worker start.");const o=String(r.agentType||r.agent||"");if(!o||!x(o))throw new w("runtime_unavailable",`Unsupported Worker Agent: ${o||"(empty)"}`,"Run shennian agent discover --compact --json and select a Worker-capable Agent type.");const i=e.getAgentSnapshot?.().find(I=>I.type===o);if(!i||i.supportsExternalWorkerTarget!==!0)throw new w("runtime_unavailable",`Worker Agent is not available on this machine: ${o}`,"Run shennian agent discover --compact --json and select a discovered Agent with supportsExternalWorkerTarget=true.");if(i.authState==="required"||i.authState==="error")throw new w("auth_required",`Worker Agent requires provider sign-in: ${o}`,"Sign in with the provider\u2019s own CLI on this computer, refresh discovery, and retry worker start.");const g=typeof r.modelId=="string"?r.modelId.trim():"";if(g&&i.models.length>0&&!i.models.some(I=>I.id===g))throw new w("model_unavailable",`The selected model was not returned by discovery for ${o}.`,`Run shennian agent discover --agent ${o} --json and choose a returned model id.`);const n=G(String(r.workDir||process.cwd())),m=`ephemeral_controller_${_()}`;e.registry.upsertManager({sessionId:m,agentSessionId:null,workDir:n,machineId:u.machineId,modelId:"manager",agentType:"manager",controllerKind:"ephemeral",status:"idle"});let y;try{y=await M(e,"/sessions/start",{managerSessionId:m,agentType:o,workDir:n,title:r.title,modelId:g||void 0,message:s},"")}catch(I){const E=e.registry.retireManager(m);for(const A of E)e.registry.completeRetiredWorker(m,A.sessionId);throw I}const f=y.body.session,W=typeof f?.sessionId=="string"?f.sessionId:"";if(!W)throw new Error("worker_start_invalid_response");const{workerRef:T,grant:R}=e.registry.issueWorkerRef({managerSessionId:m,workerSessionId:W,workDir:n,machineId:u.machineId,accountNamespaceId:u.accountNamespaceId??null});return{status:200,body:{ok:!0,workerId:W,workerRef:T,resolvedWorkDir:n,agentType:o,status:typeof f?.status=="string"?f.status:"running",expiresAt:R.expiresAt}}}const k=B[a];if(!k)throw new w("worker_command_unavailable","Unknown external Worker command.","Use worker start, read, send, wait, or stop. External Worker history cannot be listed.",404);const c=String(r.workerRef||r.ref||""),d=e.registry.resolveWorkerRefDetailed({workerRef:c,action:k.action,machineId:u.machineId,accountNamespaceId:u.accountNamespaceId??null});if(!d.ok){const s=d.reason==="expired"?"worker_ref_expired":d.reason==="revoked"?"worker_ref_revoked":"worker_ref_invalid";throw new w(s,`The Worker reference is ${d.reason} or belongs to another local scope.`,d.reason==="invalid"?"Use the exact workerRef returned by worker start. Lost references cannot be rediscovered.":"Start a new Worker if the task still needs to continue.",403)}const l=d.grant;let t;try{t=await M(e,k.managerPath,{...r,managerSessionId:l.managerSessionId,sessionId:l.workerSessionId},"")}catch(s){const o=s instanceof Error?s.message:String(s);throw o==="message is required"?new w("worker_message_required","A non-empty Worker message is required.","Provide --message or --message-file and retry worker send."):o.includes("not found")||o.includes("not registered")?new w("worker_ref_revoked","The Worker is no longer available in this controller scope.","Start a new Worker if the task still needs to continue.",410):new w("worker_command_failed",o,"Read the Worker state, correct the request, and retry once.")}if(!t)throw new w("worker_command_unavailable","The requested Worker operation is unavailable.","Use worker start, read, send, wait, or stop.",404);return{status:t.status,body:{...t.body,workerId:l.workerSessionId}}}export{w as EphemeralWorkerError,Z as handleEphemeralWorkerIpcRoute,M as handleWorkerIpcRoute};
@@ -1,6 +1,6 @@
1
- import{isAvailableBuiltinAgentType as k,managedAgentExecutionMode as P}from"@shennian/wire";import{getBuiltinAgentVersion as S,resolveBuiltinCommand as B}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as K,resolveCustomRuntimeExecutableFingerprint as x}from"../agents/custom-managed-runtime.js";import{loadConfig as $}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as W}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as T}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as q}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as D}from"./activation-policy.js";import{buildManagedAgentPermissionPrompt as E,composeManagedRoomPrompt as R}from"./managed-prompt-composer.js";import{isCurrentRegistration as f}from"./registration-authority.js";const F=4,N=6e4,O=6,V=5*6e4;class ce{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,i,t,s={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],g=[],u=new Map,m=new Set,c=[...i].sort((r,a)=>J(r.sequence,a.sequence));for(const r of t){const a=this.deps.registry.getByRegistryKey(r.registryKey);!a?.binding||!f(this.deps.registry,r)||a.binding.roomId!==e||a.binding.status!=="active"&&a.binding.status!=="paused"&&a.binding.activationQueue.length}for(const r of t){const a=this.deps.registry.getByRegistryKey(r.registryKey);if(!a?.binding||!f(this.deps.registry,r)||a.binding.roomId!==e||a.binding.activationQueue.length===0&&!a.binding.activeActivation||(m.add(a.registryKey),a.binding.status!=="active"||a.binding.activationMode==="paused")||a.binding.activationQueue.length===0)continue;const l=this.deps.observations.getBySource(a);if(!l?.workspacePath||l.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(a.registryKey,s);for(const o of a.binding.activationQueue)d.push({registration:a,input:{params:{sessionId:a.nianSessionId,text:o.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:y(a.runtimeAdapter),sessionMode:s.agentsByIdentityId?.[a.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:s.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:s.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:s.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:l.workspacePath,agentSessionId:a.sourceSessionKey,queueMessageId:`room-activation-${a.binding.bindingId}-${o.causeMessageId}`,clientMessageId:`room-activation-${a.binding.bindingId}-${o.causeMessageId}`},context:{registryKey:a.registryKey,causeMessageId:o.causeMessageId,rootCauseMessageId:o.rootCauseMessageId,sequence:o.sequence,hopCount:o.hopCount}}})}for(const r of c){const a=new Set(D(r,t).map(l=>l.registration.registryKey));for(const l of t){const o=this.deps.registry.getByRegistryKey(l.registryKey);if(!o?.binding||!f(this.deps.registry,l)||o.binding.roomId!==e||o.binding.status!=="active"&&o.binding.status!=="paused"||s.agentsByIdentityId?.[o.binding.agentIdentityId]?.configurationPending||BigInt(r.sequence)<=BigInt(o.binding.lastActivatedSequence))continue;const I=u.get(o.registryKey)??[...o.binding.mentionContext??[]];if(u.set(o.registryKey,I),a.has(o.registryKey)){const p=(r.activationCause?.hopCount??0)+(r.author.principalType==="agent"?1:0),h=r.activationCause?.rootCauseMessageId??r.id;if(p>F)this.audit(o,r.id,h,p,"suppressed","max_hops");else{const v=this.deps.registry.claimActivation({registryKey:o.registryKey,causeMessageId:r.id,windowMs:N,maxActivations:O,pauseMs:V,now:this.deps.now?.()});if(!v.allowed){v.newlyPaused&&this.audit(o,r.id,h,p,"suppressed","rate_limited"),g.push({registryKey:o.registryKey,sequence:r.sequence});continue}const b=this.deps.observations.getBySource(o);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const M=w([r]),C=o.binding.activationMode==="mention_only"?Q([...I.map(_=>_.deliveryText),M]):M;this.latestContexts.set(o.registryKey,s),d.push({registration:o,input:{params:{sessionId:o.nianSessionId,text:C,agentType:y(o.runtimeAdapter),sessionMode:s.agentsByIdentityId?.[o.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:s.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:s.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:s.agentsByIdentityId?.[o.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:o.sourceSessionKey,queueMessageId:`room-activation-${o.binding.bindingId}-${r.id}`,clientMessageId:`room-activation-${o.binding.bindingId}-${r.id}`},context:{registryKey:o.registryKey,causeMessageId:r.id,rootCauseMessageId:h,sequence:r.sequence,hopCount:p}}}),o.binding.activationMode==="mention_only"&&u.set(o.registryKey,[])}}else o.binding.activationMode==="mention_only"&&r.kind!=="system"&&r.author.membershipId!==o.binding.agentMembershipId&&(I.some(p=>p.sequence===r.sequence)||I.push({sequence:r.sequence,deliveryText:w([r])}));g.push({registryKey:o.registryKey,sequence:r.sequence})}}await this.deps.enqueueConversationBatch(d.map(r=>r.input));for(const r of m)this.deps.registry.retireLegacyActivationState(r,this.deps.now?.()??new Date);for(const[r,a]of u)this.deps.registry.getByRegistryKey(r)?.lifecycleState!=="closed"&&this.deps.registry.replaceMentionContext(r,a,this.deps.now?.()??new Date);for(const r of d)this.audit(r.registration,r.input.context.causeMessageId,r.input.context.rootCauseMessageId,r.input.context.hopCount,"queued","conversation_queue");for(const r of g)this.deps.registry.getByRegistryKey(r.registryKey)?.lifecycleState!=="closed"&&this.deps.registry.advanceCursor({registryKey:r.registryKey,cursor:"activated",sequence:r.sequence})}async prepareConversationTurn(e){const i=e.at(-1);if(!i)throw new Error("managed_room_queue_context_missing");const t=this.deps.registry.getByRegistryKey(i.registryKey);if(!t)throw new Error("managed_room_binding_unavailable");const s=await this.prepareManagedTurn(t,i.causeMessageId);return this.audit(t,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"activated","conversation_queue_dispatch"),s}async prepareDirectConversationTurn(e,i={}){const t=i.roomOrigin?this.deps.registry.getByRegistryKey(i.roomOrigin.registryKey):this.deps.registry.getByNianSessionId(e);return t?.nianSessionId!==e||!t||i.roomOrigin&&(t.registrationEpoch!==i.roomOrigin.registrationEpoch||t.binding?.bindingId!==i.roomOrigin.bindingId||t.binding.roomId!==i.roomOrigin.roomId)?null:this.prepareManagedTurn(t,void 0,i)}async prepareManagedTurn(e,i,t){if(!f(this.deps.registry,e)||!e.binding||e.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const s=this.deps.loadContext?await this.deps.loadContext(e):this.latestContexts.get(e.registryKey);if(!s)throw new Error("managed_room_prompt_context_unavailable");const d=s.agentsByIdentityId?.[e.binding.agentIdentityId];if(d?.configurationPending)throw new Error("managed_agent_configuration_pending");if(!d?.permissionPolicy)throw new Error("managed_agent_policy_missing");const g=d.sessionMode==="manager"?"manager":"standard",u=g==="manager"?{...d.managerConfig,defaultWorkerAgentType:d.managerConfig?.defaultWorkerAgentType??y(e.runtimeAdapter),defaultWorkerModelId:d.managerConfig?.defaultWorkerModelId??null}:null,m=this.deps.observations.getBySource(e);if(!m?.workspacePath||m.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const c=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:e,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:m.workspacePath}):await U({registration:e,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:m.workspacePath});if(!f(this.deps.registry,e))throw new Error("managed_room_binding_unavailable");const r=H(s,e,c),a=this.deps.issueManagedRoomContext(e.registryKey,i,t?.allowedRoomActions);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(a))throw new Error("managed_room_context_unavailable");return{agentType:y(e.runtimeAdapter),workDir:c.adapterPolicy.workDir,agentSessionId:e.sourceSessionKey,systemPrompt:t?G({managerEvent:t.managerEvent===!0,roomSendAllowed:t.allowedRoomActions?.includes("send")===!0,agentSystemPrompt:s.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId],managedAgentPolicy:c,runtimeAdapter:y(e.runtimeAdapter)}):r.systemPrompt,sessionMode:g,managerConfig:u,managerDefaultWorkerAgentType:u?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:u?.defaultWorkerModelId??null,roomContextRegistryKey:e.registryKey,managedRoomContextToken:a,managedAgentPolicy:c,managedRoomPromptSnapshot:r.snapshot}}audit(e,i,t,s,d,g){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:i,rootCauseMessageId:t,hopCount:s,outcome:d,reason:g,at:this.deps.now?.()})}}function w(n){const e=n.map(i=>{const t=i.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",s=i.attachments.length>0?`
1
+ import{isAvailableBuiltinAgentType as k,managedAgentExecutionMode as P}from"@shennian/wire";import{getBuiltinAgentVersion as S,resolveBuiltinCommand as B}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as K,resolveCustomRuntimeExecutableFingerprint as $}from"../agents/custom-managed-runtime.js";import{loadConfig as x}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as W}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as T}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as q}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as D}from"./activation-policy.js";import{buildManagedAgentPermissionPrompt as E,composeManagedRoomPrompt as R}from"./managed-prompt-composer.js";import{isCurrentRegistration as f}from"./registration-authority.js";const N=4,F=6e4,O=6,V=5*6e4;class ce{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,i,t,a={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],g=[],u=new Map,m=new Set,c=[...i].sort((o,s)=>J(o.sequence,s.sequence));for(const o of t){const s=this.deps.registry.getByRegistryKey(o.registryKey);!s?.binding||!f(this.deps.registry,o)||s.binding.roomId!==e||s.binding.status!=="active"&&s.binding.status!=="paused"&&s.binding.activationQueue.length}for(const o of t){const s=this.deps.registry.getByRegistryKey(o.registryKey);if(!s?.binding||!f(this.deps.registry,o)||s.binding.roomId!==e||s.binding.activationQueue.length===0&&!s.binding.activeActivation||(m.add(s.registryKey),s.binding.status!=="active"||s.binding.activationMode==="paused")||s.binding.activationQueue.length===0)continue;const y=this.deps.observations.getBySource(s);if(!y?.workspacePath||y.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(s.registryKey,a);for(const r of s.binding.activationQueue)d.push({registration:s,input:{params:{sessionId:s.nianSessionId,text:r.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:l(s.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:y.workspacePath,agentSessionId:s.sourceSessionKey,queueMessageId:`room-activation-${s.binding.bindingId}-${r.causeMessageId}`,clientMessageId:`room-activation-${s.binding.bindingId}-${r.causeMessageId}`},context:{registryKey:s.registryKey,causeMessageId:r.causeMessageId,rootCauseMessageId:r.rootCauseMessageId,sequence:r.sequence,hopCount:r.hopCount}}})}for(const o of c){const s=new Set(D(o,t).map(y=>y.registration.registryKey));for(const y of t){const r=this.deps.registry.getByRegistryKey(y.registryKey);if(!r?.binding||!f(this.deps.registry,y)||r.binding.roomId!==e||r.binding.status!=="active"&&r.binding.status!=="paused"||a.agentsByIdentityId?.[r.binding.agentIdentityId]?.configurationPending||BigInt(o.sequence)<=BigInt(r.binding.lastActivatedSequence))continue;const I=u.get(r.registryKey)??[...r.binding.mentionContext??[]];if(u.set(r.registryKey,I),s.has(r.registryKey)){const p=(o.activationCause?.hopCount??0)+(o.author.principalType==="agent"?1:0),h=o.activationCause?.rootCauseMessageId??o.id;if(p>N)this.audit(r,o.id,h,p,"suppressed","max_hops");else{const M=this.deps.registry.claimActivation({registryKey:r.registryKey,causeMessageId:o.id,windowMs:F,maxActivations:O,pauseMs:V,now:this.deps.now?.()});if(!M.allowed){M.newlyPaused&&this.audit(r,o.id,h,p,"suppressed","rate_limited"),g.push({registryKey:r.registryKey,sequence:o.sequence});continue}const b=this.deps.observations.getBySource(r);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const v=w([o]),C=r.binding.activationMode==="mention_only"?Q([...I.map(_=>_.deliveryText),v]):v;this.latestContexts.set(r.registryKey,a),d.push({registration:r,input:{params:{sessionId:r.nianSessionId,text:C,agentType:l(r.runtimeAdapter),sessionMode:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:a.agentsByIdentityId?.[r.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:r.sourceSessionKey,queueMessageId:`room-activation-${r.binding.bindingId}-${o.id}`,clientMessageId:`room-activation-${r.binding.bindingId}-${o.id}`},context:{registryKey:r.registryKey,causeMessageId:o.id,rootCauseMessageId:h,sequence:o.sequence,hopCount:p}}}),r.binding.activationMode==="mention_only"&&u.set(r.registryKey,[])}}else r.binding.activationMode==="mention_only"&&o.kind!=="system"&&o.author.membershipId!==r.binding.agentMembershipId&&(I.some(p=>p.sequence===o.sequence)||I.push({sequence:o.sequence,deliveryText:w([o])}));g.push({registryKey:r.registryKey,sequence:o.sequence})}}await this.deps.enqueueConversationBatch(d.map(o=>o.input));for(const o of m)this.deps.registry.retireLegacyActivationState(o,this.deps.now?.()??new Date);for(const[o,s]of u)this.deps.registry.getByRegistryKey(o)?.lifecycleState!=="closed"&&this.deps.registry.replaceMentionContext(o,s,this.deps.now?.()??new Date);for(const o of d)this.audit(o.registration,o.input.context.causeMessageId,o.input.context.rootCauseMessageId,o.input.context.hopCount,"queued","conversation_queue");for(const o of g)this.deps.registry.getByRegistryKey(o.registryKey)?.lifecycleState!=="closed"&&this.deps.registry.advanceCursor({registryKey:o.registryKey,cursor:"activated",sequence:o.sequence})}async prepareConversationTurn(e){const i=e.at(-1);if(!i)throw new Error("managed_room_queue_context_missing");const t=this.deps.registry.getByRegistryKey(i.registryKey);if(!t)throw new Error("managed_room_binding_unavailable");const a=await this.prepareManagedTurn(t,i.causeMessageId);return this.audit(t,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"activated","conversation_queue_dispatch"),a}async prepareDirectConversationTurn(e,i={}){const t=i.roomOrigin?this.deps.registry.getByRegistryKey(i.roomOrigin.registryKey):this.deps.registry.getByNianSessionId(e);return t?.nianSessionId!==e||!t||i.roomOrigin&&(t.registrationEpoch!==i.roomOrigin.registrationEpoch||t.binding?.bindingId!==i.roomOrigin.bindingId||t.binding.roomId!==i.roomOrigin.roomId)?null:this.prepareManagedTurn(t,void 0,i)}async prepareManagedTurn(e,i,t){if(!f(this.deps.registry,e)||!e.binding||e.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const a=this.deps.loadContext?await this.deps.loadContext(e):this.latestContexts.get(e.registryKey);if(!a)throw new Error("managed_room_prompt_context_unavailable");const d=a.agentsByIdentityId?.[e.binding.agentIdentityId];if(d?.configurationPending)throw new Error("managed_agent_configuration_pending");if(!d?.permissionPolicy)throw new Error("managed_agent_policy_missing");const g=d.sessionMode==="manager"?"manager":"standard",u=g==="manager"?{...d.managerConfig,defaultWorkerAgentType:d.managerConfig?.defaultWorkerAgentType??l(e.runtimeAdapter),defaultWorkerModelId:d.managerConfig?.defaultWorkerModelId??null}:null,m=this.deps.observations.getBySource(e);if(!m?.workspacePath||m.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const c=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:e,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:m.workspacePath}):await U({registration:e,permissionPolicy:d.permissionPolicy,permissionConfirmation:d.permissionConfirmation,workDir:m.workspacePath});if(!f(this.deps.registry,e))throw new Error("managed_room_binding_unavailable");const o=H(a,e,c),s=this.deps.issueManagedRoomContext(e.registryKey,i,t?.allowedRoomActions);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(s))throw new Error("managed_room_context_unavailable");return{agentType:l(e.runtimeAdapter),workDir:c.adapterPolicy.workDir,agentSessionId:e.sourceSessionKey,systemPrompt:t?G({managerEvent:t.managerEvent===!0,roomSendAllowed:t.allowedRoomActions?.includes("send")===!0,agentSystemPrompt:a.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId],managedAgentPolicy:c,runtimeAdapter:l(e.runtimeAdapter)}):o.systemPrompt,sessionMode:g,managerConfig:u,managerDefaultWorkerAgentType:u?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:u?.defaultWorkerModelId??null,roomContextRegistryKey:e.registryKey,managedRoomContextToken:s,managedAgentPolicy:c,managedRoomPromptSnapshot:o.snapshot}}audit(e,i,t,a,d,g){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:i,rootCauseMessageId:t,hopCount:a,outcome:d,reason:g,at:this.deps.now?.()})}}function w(n){const e=n.map(i=>{const t=i.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",a=i.attachments.length>0?`
2
2
  ${i.attachments.map(L).join(`
3
- `)}`:"";return{speaker:i.author.displayName,body:`${t}${s}`}});return A(e)}function L(n){return`\u9644\u4EF6\uFF1A${`${j(n.name)} \xB7 ${z(n.byteSize)}`}\uFF5C${n.mimeType}\uFF5CattachmentId=${n.attachmentId}`}function j(n){return String(n||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function z(n){const e=Number(n);if(!Number.isFinite(e)||e<0)return`${n} B`;if(e<1024)return`${e} B`;const i=["KB","MB","GB","TB"];let t=e,s=-1;do t/=1024,s+=1;while(t>=1024&&s<i.length-1);const d=t>=100?0:t>=10?1:2;return`${t.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${i[s]}`}function Q(n){return A(n.map(X))}function X(n){const e=n.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const i=n.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(i)return{speaker:i[1],body:i[2]};const t=n.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const s=n.match(/^([^\n]+)\n([\s\S]*)$/);return s?{speaker:s[1],body:s[2]}:{speaker:"\u7FA4\u6210\u5458",body:n.trim()}}function A(n){if(n.length===1){const[e]=n;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
3
+ `)}`:"";return{speaker:i.author.displayName,body:`${t}${a}`}});return A(e)}function L(n){return`\u9644\u4EF6\uFF1A${`${j(n.name)} \xB7 ${z(n.byteSize)}`}\uFF5C${n.mimeType}\uFF5CattachmentId=${n.attachmentId}`}function j(n){return String(n||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function z(n){const e=Number(n);if(!Number.isFinite(e)||e<0)return`${n} B`;if(e<1024)return`${e} B`;const i=["KB","MB","GB","TB"];let t=e,a=-1;do t/=1024,a+=1;while(t>=1024&&a<i.length-1);const d=t>=100?0:t>=10?1:2;return`${t.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${i[a]}`}function Q(n){return A(n.map(X))}function X(n){const e=n.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const i=n.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(i)return{speaker:i[1],body:i[2]};const t=n.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(t)return{speaker:t[1],body:t[2]};const a=n.match(/^([^\n]+)\n([\s\S]*)$/);return a?{speaker:a[1],body:a[2]}:{speaker:"\u7FA4\u6210\u5458",body:n.trim()}}function A(n){if(n.length===1){const[e]=n;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
4
4
  ${e.body}`}return`[\u7FA4\u6D88\u606F]
5
5
  ${n.map(e=>`<${e.speaker}>
6
6
  ${e.body}`).join(`
@@ -10,4 +10,4 @@ ${i}
10
10
  \u4E0D\u5F97\u731C\u6D4B\u6216\u5C55\u793A Room\u3001Agent\u3001Session\u3001Binding\u3001Membership\u3001Machine \u6807\u8BC6\u6216\u4EFB\u4F55\u51ED\u636E\u3002`,e,t?`[\u667A\u80FD\u4F53\u79C1\u6709\u7CFB\u7EDF\u63D0\u793A\u8BCD]
11
11
  ${t}`:""].filter(Boolean).join(`
12
12
 
13
- `)}function H(n,e,i){const t=e.binding?n.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!t)throw new Error("managed_room_prompt_incomplete");return R({runtimeAdapter:y(e.runtimeAdapter),agent:{displayName:t.displayName,description:t.description,systemPrompt:n.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId],configurationVersion:t.configurationVersion},room:{name:n.roomName,activationMode:e.binding.activationMode,members:n.members},runtimePermissions:{requestedPolicy:i.requestedPolicy,enforcement:i.enforcement,resolvedWorkDir:i.adapterPolicy.workDir}})}async function U(n){const e=y(n.registration.runtimeAdapter),i=k(e)?B(e):null,t=e.startsWith("custom:")?$().customAgents?.[e.slice(7)]:void 0;t&&K(e,t);const s=n.runtime?.version??(i?S(e,i):t?.caps.version),d=n.runtime?{platform:n.runtime.runtimePlatform??process.platform,isWsl:n.runtime.runtimeIsWsl??!1,executableFingerprint:n.runtime.executableFingerprint}:i?W(i):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:t?x(t):void 0},g={agentType:e,runtimeVersion:s,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},u=q(g),m=Z({confirmation:n.permissionConfirmation,policy:n.permissionPolicy,runtimeAdapter:e,protection:u}),c=await T({...g,policy:n.permissionPolicy,workDir:n.workDir,hostFullAccessConfirmed:m&&n.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:m&&n.permissionConfirmation?.limitedProtection});if(!c.ok){const r=c.unsupportedCapabilities?.length?`:${c.unsupportedCapabilities.join(",")}`:"";throw new Error(`${c.errorCode}${r}`)}return c.value}function Z(n){const{confirmation:e,policy:i,runtimeAdapter:t,protection:s}=n,d=P(i);return!!(e&&e.policyVersion===i.version&&e.runtimeAdapter===t&&e.runtimeVersion===s.runtimeVersion&&e.platform===s.platform&&e.executableFingerprint===s.executableFingerprint&&(e.executionMode??"restricted")===d&&e.hostFullAccess===(d==="native-full"||i.fileAccess==="host-full")&&e.limitedProtection===(d==="restricted"&&s.level!=="hard"))}function y(n){return n==="claude_code"?"claude":n}function J(n,e){const i=BigInt(n),t=BigInt(e);return i<t?-1:i>t?1:0}export{ce as RoomActivationService,Z as managedAgentPermissionConfirmationMatches,U as resolveManagedAgentPolicy};
13
+ `)}function H(n,e,i){const t=e.binding?n.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!t)throw new Error("managed_room_prompt_incomplete");return R({runtimeAdapter:l(e.runtimeAdapter),agent:{displayName:t.displayName,description:t.description,systemPrompt:n.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId],configurationVersion:t.configurationVersion},room:{name:n.roomName,activationMode:e.binding.activationMode,members:n.members},runtimePermissions:{requestedPolicy:i.requestedPolicy,enforcement:i.enforcement,resolvedWorkDir:i.adapterPolicy.workDir}})}async function U(n){const e=l(n.registration.runtimeAdapter),i=k(e)?B(e):null,t=e.startsWith("custom:")?x().customAgents?.[e.slice(7)]:void 0;t&&K(e,t);const a=n.runtime?.version??(i?S(e,i):t?.caps.version),d=n.runtime?{platform:n.runtime.runtimePlatform??process.platform,isWsl:n.runtime.runtimeIsWsl??!1,executableFingerprint:n.runtime.executableFingerprint}:i?W(i):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:t?$(t):void 0},g={agentType:e,runtimeVersion:a,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},u=q(g),m=Z({confirmation:n.permissionConfirmation,policy:n.permissionPolicy,runtimeAdapter:e,protection:u}),c=await T({...g,policy:n.permissionPolicy,workDir:n.workDir,hostFullAccessConfirmed:m&&n.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:m&&n.permissionConfirmation?.limitedProtection});if(!c.ok){const o=c.unsupportedCapabilities?.length?`:${c.unsupportedCapabilities.join(",")}`:"";throw new Error(`${c.errorCode}${o}`)}return c.value}function Z(n){const{confirmation:e,policy:i,runtimeAdapter:t}=n,a=P(i);return!!(e&&e.policyVersion===i.version&&e.runtimeAdapter===t&&(e.executionMode??"restricted")===a&&e.hostFullAccess===(a==="native-full"||i.fileAccess==="host-full"))}function l(n){return n==="claude_code"?"claude":n}function J(n,e){const i=BigInt(n),t=BigInt(e);return i<t?-1:i>t?1:0}export{ce as RoomActivationService,Z as managedAgentPermissionConfirmationMatches,U as resolveManagedAgentPolicy};
@@ -29,6 +29,7 @@ export declare class SessionManager {
29
29
  private managedRoomContextRevoker;
30
30
  private managedRoomExecutor;
31
31
  private trustedProjectFsRequestResolver;
32
+ private getAgentSnapshot;
32
33
  scheduleHandler?: (req: ReqFrame) => Promise<void>;
33
34
  getScheduleQueue(): ChatQueueManager;
34
35
  private sessions;
@@ -52,7 +53,7 @@ export declare class SessionManager {
52
53
  private recoveryPass;
53
54
  private prepareManagedRecovery;
54
55
  private projectFsHandleStore;
55
- constructor(client: CliRelayClient, nativeFusion?: NativeSessionFusionService | null, cliVersion?: string | undefined, upgradePolicyController?: UpgradePolicyController | null, managedRoomBinder?: ManagedRoomBinder | null, remoteAccessPauseController?: RemoteAccessPauseController | null, managedRoomContextRevoker?: ((token: string) => void) | null, managedRoomExecutor?: ManagedRoomExecutor | null, runJournalOverride?: ActiveRunJournal | null, trustedProjectFsRequestResolver?: TrustedProjectFsRequestResolver | null);
56
+ constructor(client: CliRelayClient, nativeFusion?: NativeSessionFusionService | null, cliVersion?: string | undefined, upgradePolicyController?: UpgradePolicyController | null, managedRoomBinder?: ManagedRoomBinder | null, remoteAccessPauseController?: RemoteAccessPauseController | null, managedRoomContextRevoker?: ((token: string) => void) | null, managedRoomExecutor?: ManagedRoomExecutor | null, runJournalOverride?: ActiveRunJournal | null, trustedProjectFsRequestResolver?: TrustedProjectFsRequestResolver | null, getAgentSnapshot?: (() => import('@shennian/wire').AgentInfo[]) | null);
56
57
  executeManagedManagerAction(input: Parameters<ManagedManagerExecutor>[0]): Promise<import("../daemon-ipc/json.js").JsonValue>;
57
58
  private getRuntime;
58
59
  resumePendingAcpAuthentication(agentType: string): Promise<boolean>;
@@ -1 +1 @@
1
- import{SESSION_SYNC_PROTOCOL_VERSION as g,SESSION_SYNC_DATA_EPOCH as f}from"@shennian/wire";import h from"node:crypto";import{getRegisteredAgents as k,unregisterAgent as I}from"../agents/adapter.js";import{resetCommandSpecCache as A}from"../agents/command-spec.js";import{loadConfig as T}from"../config/index.js";import{handleUpgradeStart as P,handleUpgradeStatus as C}from"../commands/upgrade.js";import{handleAgentsRefresh as M,handleModelsRefresh as D,refreshMachineAgents as x}from"./handlers/agents.js";import{AcpDiscoveryWatcher as E}from"../agents/acp/watcher.js";import{handleAgentCapabilitiesList as F}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as O,handleAgentConfigGet as Q,handleAgentConfigTest as _,handleAgentConfigUpsert as j}from"./handlers/agent-config.js";import{handleChatAbort as B,handleChatSend as U}from"./handlers/chat.js";import{recoverJournaledRun as z}from"./handlers/chat-recovery.js";import{handleSessionRefresh as W}from"./handlers/session-refresh.js";import{handleSessionToolDetail as H}from"./handlers/tool-detail.js";import{handleSessionTitleSet as $}from"./handlers/title.js";import{handleSessionMessageStatus as J}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as N}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as V}from"./handlers/voice-window.js";import{handleRemoteAccessPause as L}from"./handlers/remote-access.js";import{cleanupPendingTransfers as G,handleFsLs as Y,handleFsRead as K,handleFsRename as X,handleFsWrite as Z,handleFsTransferAbort as q,handleFsTransferChunk as ee,handleFsTransferFinish as se,handleFsTransferStart as te,handleFsExportMarkdownPdf as ae,handleFsExportMarkdownPdfSetup as re,handleFsArchiveZip as ne}from"./handlers/fs.js";import{handleRegionProbe as ie,handleRegionSwitch as oe,handleUpgradeSetPolicy as ce}from"./handlers/control.js";import{ManagerRuntimeService as le,setManagerRuntimeService as v}from"../manager/runtime.js";import{ChatQueueManager as de}from"./queue.js";import{createAuthorizedFsRoot as he,createAuthorizedFsRootAsync as ue,resolveAuthorizedPath as me,resolveAuthorizedPathAsync as pe,resolveSessionWorkDir as ge}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as fe}from"../agents/custom.js";import{handleManagedRoomBind as ve,handleManagedRoomRestart as ye,discardPreBindingSession as Re}from"./handlers/room-managed.js";import{getDaemonInstallationId as u,listSessionRecords as m,purgeCanonicalSession as be,purgeSessionRecord as Se,recordSession as we,upsertMessage as ke}from"./store.js";import{requestSessionIndexPublish as Ie}from"./index-publisher.js";import{reportUpgradeAggregateMetric as Ae}from"../log-reporter.js";import{PersonalHistorySubscriptions as Te}from"./history-subscriptions.js";import{sweepAttachmentTransfers as Pe}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as Ce}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as Me}from"./tool-detail-store.js";import{readDeletionSyncRevision as De,writeDeletionSyncRevision as y}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as xe,clearDeletionSyncFailure as Ee,DeletionSyncRetryWaitError as Fe,DeletionSyncTerminalError as Oe,recordDeletionSyncFailure as p}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Qe}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as c}from"../personal-sync/epoch.js";import{ActiveRunJournal as _e}from"./run-recovery/journal.js";import{RunRecoveryBarrier as je}from"./run-recovery/barrier.js";import{RunRecoveryCoordinator as Be}from"./run-recovery/coordinator.js";import{ProjectFsHandleStore as Ue}from"./project-fs-store.js";import{handleProjectFsRequest as ze}from"./handlers/project-fs.js";import{handleAgentPermissionResponse as We,handleAcpAgentRequest as He}from"./handlers/agent-control.js";import{validateDeletionSyncResponse as $e}from"./deletion-sync-validation.js";import{dispatchManagedChat as Je,dispatchQueuedChat as Ne,interruptQueuedChat as Ve}from"./queued-chat-dispatch.js";const Le=50;import{resolveSessionWorkDir as Zs}from"../fs/boundary.js";function Ge(){return"\u795E\u5FF5\u5347\u7EA7\u4E2D\u65AD\u4E86\u672C\u6B21\u56DE\u590D\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u91CD\u8BD5\u6216\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002"}class Ys{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;trustedProjectFsRequestResolver;scheduleHandler;getScheduleQueue(){return this.chatQueue}sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;acpDiscoveryWatcher;runJournal;recoveryBarrier=new je;recoveryCoordinator;recoveryPass=null;prepareManagedRecovery;projectFsHandleStore=new Ue;constructor(e,t=null,s,n=null,r=null,d=null,o=null,R=null,b=null,S=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=n,this.managedRoomBinder=r,this.remoteAccessPauseController=d,this.managedRoomContextRevoker=o,this.managedRoomExecutor=R,this.trustedProjectFsRequestResolver=S,this.runJournal=b??new _e(u());for(const a of this.runJournal.loadPendingReconciliation().records)this.recoveryBarrier.begin(a.sessionId);this.recoveryCoordinator=new Be({journal:this.runJournal,barrier:this.recoveryBarrier,recover:(a,i)=>z(this.getRuntime(),a,i),commitTerminal:(a,i)=>this.commitRecoveryTerminal(a,i),isCanonicalTerminal:a=>{const i=m().find(w=>w.sessionId===a.sessionId),l=`terminal:${h.createHash("sha256").update(`${a.runId}\0`).digest("hex")}`;return i?.lastAttentionId===l},onOutcome:a=>Ae({kind:"recovery",adapterType:a.adapterType,result:a.result,stage:a.result==="continued"?"continue":a.result==="reattached"?"reattach":a.result==="resumed"?"resume":a.result==="terminal_reconciled"?"inspect":"terminal",durationMs:a.durationMs,retries:Math.max(0,a.attempts-1),...a.reason?{errorCode:a.reason}:{}})}),this.managerRuntime=new le({getRuntime:()=>this.getRuntime(),dispatchReq:a=>this.handleReq(a,{propagateErrors:!0})}),this.chatQueue=new de({getRuntime:()=>this.getRuntime(),dispatchReq:a=>this.handleReq(a),dispatchManagedReq:(a,i)=>Je(this.getRuntime(),a,i),dispatchQueuedReq:(a,i)=>Ne(this.getRuntime(),a,i),interruptQueuedSession:a=>Ve(this.getRuntime(),a)}),this.historySubscriptions=new Te(this.client),this.nativeCleanupOutbox=new Qe({logger:console.info}),this.acpDiscoveryWatcher=new E(async()=>{A(),await x(this.getRuntime())}),this.nativeFusion?.setExternalTerminalHandler?.(a=>{this.handleExternalTerminal(a)}),this.nativeFusion?.setExternalClaimHandler?.((a,i)=>{this.chatQueue.noteExternalClaim(a,i)}),this.nativeCleanupOutbox.start(),v(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents();try{this.acpDiscoveryWatcher.start()}catch(a){console.warn(`[acp.discovery] watcher unavailable: ${a instanceof Error?a.message:String(a)}`)}this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(a=>{console.error("[session.activity] managed probe failed",a)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const a=new Set([...m().map(i=>i.workDir),...Array.from(this.pendingTransfers.values()).map(i=>i.rootPath)]);for(const i of a)try{Pe(this.resolvePath(i))}catch(l){console.error("[attachment.cleanup] failed",l instanceof Error?l.message:String(l))}},6e4),this.attachmentCleanupTimer.unref?.()}executeManagedManagerAction(e){return this.managerRuntime.executeScopedWorkerAction(e.managerSessionId,e.action,e.params,e.roomOrigin)}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),resolveAuthorizedPathAsync:(e,t)=>this.resolveAuthorizedPathAsync(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor,runJournal:this.runJournal,recoveryBarrier:this.recoveryBarrier,prepareManagedRecovery:this.prepareManagedRecovery}}async resumePendingAcpAuthentication(e){const t=[...this.sessions.values()].find(s=>s.agentType===e&&s.adapter.hasPendingAuthenticationRetry?.());return t?.adapter.resumeAfterExternalAuthentication?(await t.adapter.resumeAfterExternalAuthentication(),!0):!1}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}prepareForUpgrade(e){this.recoveryBarrier.freezeDispatch();const t=this.runJournal.markPlannedUpgrade(e);return this.runJournal.sync(),{activeRunCount:t}}cancelUpgradePreparation(){this.recoveryBarrier.unfreezeDispatch()}async recoverInterruptedRuns(){this.recoveryPass??=this.recoveryCoordinator.recoverAll(),await this.recoveryPass}getPendingRecoveryCount(){return this.runJournal.loadPendingReconciliation().records.length}revokeProjectFsHandles(){this.projectFsHandleStore.revokeAll()}async commitRecoveryTerminal(e,t){const s=this.sessions.get(e.sessionId);s&&(s.currentRunId=null,s.currentRunPhase=null,s.journalRunId=null,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null)),this.runTextAcc.delete(`${e.sessionId}:${e.providerRunId??e.runId}`),this.publishSessionActivity(e.sessionId,null);const n=Ge();ke(e.sessionId,{id:`upgrade-recovery-terminal-${e.runId}`,sessionId:e.sessionId,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:"error",content:n,error:!0,recoveryState:t.result,recoveryReason:t.reason})}),we({sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,status:"failed",attentionId:`upgrade-recovery:${e.runId}`,lastActivityAt:new Date().toISOString()}),this.managerRuntime.noteAgentEvent(e.sessionId,{state:"error",runId:e.providerRunId??e.runId,seq:e.lastObservedEventSeq+1,message:n}),Ie(this.client);const r=e.lastObservedEventSeq+1;this.client.sendAgentEvent({type:"event",event:"agent",payload:t.result==="interrupted_by_upgrade"?{state:"interrupted_by_upgrade",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:r,reason:t.reason}:{state:"recovery_failed",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:r,reason:t.reason,...t.message?{message:t.message}:{}},seq:r,id:`agent-recovery-terminal-${e.runId}`}),this.chatQueue.noteTerminal(e.sessionId)}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}setManagedAgentDirectTurnPreparer(e){this.chatQueue.setManagedAgentDirectTurnPreparer(e)}setManagedRecoveryPreparer(e){this.prepareManagedRecovery=e}async enqueueManagedRoomBatch(e){const t=e.map(s=>{const n=m().find(r=>r.sessionId===s.params.sessionId);return{context:s.context,params:{...s.params,modelId:n?.modelId??s.params.modelId,sessionMode:n?.sessionMode??s.params.sessionMode,managerConfig:n?.managerConfig??s.params.managerConfig,managerDefaultWorkerAgentType:n?.managerDefaultWorkerAgentType??s.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:n?.managerDefaultWorkerModelId??s.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(t)}async retireManagedRoomSession(e){this.chatQueue.purgeSession(e),await Re(this.getRuntime(),e)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}async synchronizeSessionDeletions(){xe();try{await this.synchronizeSessionDeletionsOnce(),Ee()}catch(e){if(e instanceof Fe||e instanceof Oe)throw e;const t=e instanceof Error?e.message:String(e),s=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(t)?"transport_unavailable":/invalid (?:deletion sync|session deletion)|deletion revision gap/i.test(t)?"invalid_deletion_sync_response":"deletion_sync_failed";throw console.warn("[session.deletions] retrying failed synchronization",{code:s}),p({code:s,retryable:!0}),e}}async synchronizeSessionDeletionsOnce(){const e=u(),t=De(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${h.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:g,dataEpoch:f,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const o=s.rejection;throw o?(p({code:o.code,retryable:o.retryable&&o.requiredAction==="retry"}),new Error(o.code)):(p({code:"unstructured_rejection",retryable:!0}),new Error(s.error??"unstructured_rejection"))}const n=s.payload;c(n),$e(n,t);let r=n.baselineRevision;r>t&&y(r);for(const o of n.deletions)o.deletionRevision<=r||(await this.applySessionDeletion(o),r=o.deletionRevision,y(r));if(r!==n.latestRevision)throw new Error(`deletion revision gap: applied ${r}, latest ${n.latestRevision}`);if(r===0)return;const d=await this.client.sendReq({type:"req",id:`session-deletions-ack-${h.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:g,dataEpoch:f,daemonInstallationId:e,deletionRevision:r}});if(!d.ok)throw new Error(d.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new Ce({daemonInstallationId:u()});t.recordSession(e);for(const n of e.sources){const r=t.find(n.agentType,n.sourceSessionKey);r&&r.sessionId!==e.sessionId||t.record({...e,...n})}this.runJournal.markSessionAborted(e.sessionId),this.recoveryBarrier.end(e.sessionId);const s=this.sessions.get(e.sessionId);if(s){this.revokeManagedRoomToken(s);try{await s.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),Me(e.sessionId),be(e.sessionId),Se(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async handleExternalTerminal(e){const t=this.sessions.get(e);t&&(t.journalRunId&&(this.recoveryCoordinator.completeFromProvider(e,t.journalRunId),t.journalRunId=null),this.sessions.delete(e),this.revokeManagedRoomToken(t),t.heartbeatTimer&&clearInterval(t.heartbeatTimer),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const n=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:n,canStop:s.canStop??!0});const r=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:r,runPhase:s.runPhase,canStop:s.canStop??!0},seq:r,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of k())t.startsWith("custom:")&&I(t);const e=T();for(const[t,s]of Object.entries(e.customAgents??{}))fe(t,s)}async handleReq(e,t={}){const s=this.getRuntime();let n;this.activeRequests++;try{switch(e.method){case"schedule.deliver":if(!this.scheduleHandler)throw new Error("schedules_unavailable");await this.scheduleHandler(e);break;case"chat.send":if(e.params.scheduledDelivery)throw new Error("schedule_source_untrusted");c(e.params),this.recoveryBarrier.isDispatchFrozen()||this.recoveryBarrier.isRecovering(String(e.params.sessionId??""))?n={durableQueueHandoff:await this.chatQueue.handleEnqueue({...e,method:"chat.enqueue"})}:await U(s,e);break;case"chat.enqueue":c(e.params),n={durableQueueHandoff: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.queue.promote":c(e.params),await this.chatQueue.handlePromote(e);break;case"chat.abort":c(e.params),await B(s,e);break;case"session.refresh":c(e.params),await W(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":J(s,e);break;case"session.tool.detail":await H(s,e);break;case"voice.session.body.window":V(s,e);break;case"session.title.set":await $(s,e);break;case"room.agent.bind-managed":await ve(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await ye(s,this.managedRoomBinder,e);break;case"fs.ls":await Y(s,e);break;case"project.fs.open":case"project.fs.list":case"project.fs.read":case"project.fs.close":await ze(this.client,this.projectFsHandleStore,e,this.trustedProjectFsRequestResolver);break;case"fs.read":await K(s,e);break;case"fs.write":await Z(s,e);break;case"fs.export.markdown-pdf":await ae(s,e);break;case"fs.export.markdown-pdf.setup":await re(s,e);break;case"fs.archive.zip":await ne(s,e);break;case"fs.rename":await X(s,e);break;case"fs.transfer.start":await te(s,e);break;case"fs.transfer.chunk":await ee(s,e);break;case"fs.transfer.finish":await se(s,e);break;case"fs.transfer.abort":await q(s,e);break;case"region.probe":await ie(s,e);break;case"region.switch":await oe(s,e);break;case"upgrade.start":await P(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0,prepareForUpgrade:r=>this.prepareForUpgrade(r),cancelUpgradePreparation:()=>this.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()}});break;case"upgrade.status":await C(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await ce(s,e);break;case"machine.remote-access.pause":await L(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await M(s,e);break;case"agent.permission.respond":await We(s,e);break;case"agent.auth.start":case"agent.auth.logout":case"agent.session-options.get":case"agent.external-sessions.list":case"agent.external-sessions.import":case"agent.external-sessions.close":case"agent.external-sessions.delete":await He(s,e);break;case"models.refresh":await D(s,e);break;case"agent.config.get":await Q(s,e);break;case"agent.config.upsert":await j(s,e);break;case"agent.config.clear":await O(s,e);break;case"agent.config.test":await _(s,e);break;case"agent.workspace.validate":await N(s,e);break;case"agent.capabilities.list":await F(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(r){if(t.propagateErrors)throw r;this.client.sendRes({type:"res",id:e.id,ok:!1,error:r instanceof Error?r.message:String(r)})}finally{this.activeRequests--}return n}evictIdleSessions(){if(this.sessions.size<Le)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(this.revokeManagedRoomToken(e.session),e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return ge(e)}resolveAuthorizedPath(e,t){return me(e,he(t))}async resolveAuthorizedPathAsync(e,t){return pe(e,await ue(t))}async cleanup(){this.chatQueue.stop(),this.projectFsHandleStore.revokeAll(),this.acpDiscoveryWatcher.stop(),this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),G(this.getRuntime()),await this.managerRuntime.stop(),v(null)}revokeManagedRoomToken(e){const t=e.managedRoomContextToken;e.managedRoomContextToken=null,t&&this.managedRoomContextRevoker?.(t)}}export{Ys as SessionManager,Zs as resolveSessionWorkDir,Ge as upgradeRecoveryVisibleMessage};
1
+ import{SESSION_SYNC_PROTOCOL_VERSION as g,SESSION_SYNC_DATA_EPOCH as f}from"@shennian/wire";import h from"node:crypto";import{getRegisteredAgents as I,unregisterAgent as A}from"../agents/adapter.js";import{resetCommandSpecCache as T}from"../agents/command-spec.js";import{loadConfig as P}from"../config/index.js";import{handleUpgradeStart as C,handleUpgradeStatus as M}from"../commands/upgrade.js";import{handleAgentsRefresh as D,handleModelsRefresh as x,refreshMachineAgents as E}from"./handlers/agents.js";import{AcpDiscoveryWatcher as F}from"../agents/acp/watcher.js";import{handleAgentCapabilitiesList as O}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as Q,handleAgentConfigGet as _,handleAgentConfigTest as j,handleAgentConfigUpsert as B}from"./handlers/agent-config.js";import{handleChatAbort as U,handleChatSend as z}from"./handlers/chat.js";import{recoverJournaledRun as W}from"./handlers/chat-recovery.js";import{handleSessionRefresh as H}from"./handlers/session-refresh.js";import{handleSessionToolDetail as $}from"./handlers/tool-detail.js";import{handleSessionTitleSet as J}from"./handlers/title.js";import{handleSessionMessageStatus as N}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as V}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as L}from"./handlers/voice-window.js";import{handleRemoteAccessPause as G}from"./handlers/remote-access.js";import{cleanupPendingTransfers as Y,handleFsLs as K,handleFsRead as X,handleFsRename as Z,handleFsWrite as q,handleFsTransferAbort as ee,handleFsTransferChunk as te,handleFsTransferFinish as se,handleFsTransferStart as ae,handleFsExportMarkdownPdf as ne,handleFsExportMarkdownPdfSetup as re,handleFsArchiveZip as ie}from"./handlers/fs.js";import{handleRegionProbe as oe,handleRegionSwitch as ce,handleUpgradeSetPolicy as le}from"./handlers/control.js";import{ManagerRuntimeService as de,setManagerRuntimeService as v}from"../manager/runtime.js";import{ChatQueueManager as he}from"./queue.js";import{createAuthorizedFsRoot as ue,createAuthorizedFsRootAsync as me,resolveAuthorizedPath as pe,resolveAuthorizedPathAsync as ge,resolveSessionWorkDir as fe}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as ve}from"../agents/custom.js";import{handleManagedRoomBind as ye,handleManagedRoomRestart as Re,discardPreBindingSession as be}from"./handlers/room-managed.js";import{getDaemonInstallationId as u,listSessionRecords as m,purgeCanonicalSession as Se,purgeSessionRecord as we,recordSession as ke,upsertMessage as Ie}from"./store.js";import{requestSessionIndexPublish as Ae}from"./index-publisher.js";import{reportUpgradeAggregateMetric as Te}from"../log-reporter.js";import{PersonalHistorySubscriptions as Pe}from"./history-subscriptions.js";import{sweepAttachmentTransfers as Ce}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as Me}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as De}from"./tool-detail-store.js";import{readDeletionSyncRevision as xe,writeDeletionSyncRevision as y}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as Ee,clearDeletionSyncFailure as Fe,DeletionSyncRetryWaitError as Oe,DeletionSyncTerminalError as Qe,recordDeletionSyncFailure as p}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as _e}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as c}from"../personal-sync/epoch.js";import{ActiveRunJournal as je}from"./run-recovery/journal.js";import{RunRecoveryBarrier as Be}from"./run-recovery/barrier.js";import{RunRecoveryCoordinator as Ue}from"./run-recovery/coordinator.js";import{ProjectFsHandleStore as ze}from"./project-fs-store.js";import{handleProjectFsRequest as We}from"./handlers/project-fs.js";import{handleAgentPermissionResponse as He,handleAcpAgentRequest as $e}from"./handlers/agent-control.js";import{validateDeletionSyncResponse as Je}from"./deletion-sync-validation.js";import{dispatchManagedChat as Ne,dispatchQueuedChat as Ve,interruptQueuedChat as Le}from"./queued-chat-dispatch.js";const Ge=50;import{resolveSessionWorkDir as qt}from"../fs/boundary.js";function Ye(){return"\u795E\u5FF5\u5347\u7EA7\u4E2D\u65AD\u4E86\u672C\u6B21\u56DE\u590D\u3002\u539F\u4F1A\u8BDD\u5DF2\u4FDD\u7559\uFF0C\u53EF\u91CD\u8BD5\u6216\u7EE7\u7EED\u53D1\u9001\u65B0\u6D88\u606F\u3002"}class Kt{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;trustedProjectFsRequestResolver;getAgentSnapshot;scheduleHandler;getScheduleQueue(){return this.chatQueue}sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;acpDiscoveryWatcher;runJournal;recoveryBarrier=new Be;recoveryCoordinator;recoveryPass=null;prepareManagedRecovery;projectFsHandleStore=new ze;constructor(e,s=null,t,r=null,n=null,d=null,o=null,R=null,b=null,S=null,w=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=r,this.managedRoomBinder=n,this.remoteAccessPauseController=d,this.managedRoomContextRevoker=o,this.managedRoomExecutor=R,this.trustedProjectFsRequestResolver=S,this.getAgentSnapshot=w,this.runJournal=b??new je(u());for(const a of this.runJournal.loadPendingReconciliation().records)this.recoveryBarrier.begin(a.sessionId);this.recoveryCoordinator=new Ue({journal:this.runJournal,barrier:this.recoveryBarrier,recover:(a,i)=>W(this.getRuntime(),a,i),commitTerminal:(a,i)=>this.commitRecoveryTerminal(a,i),isCanonicalTerminal:a=>{const i=m().find(k=>k.sessionId===a.sessionId),l=`terminal:${h.createHash("sha256").update(`${a.runId}\0`).digest("hex")}`;return i?.lastAttentionId===l},onOutcome:a=>Te({kind:"recovery",adapterType:a.adapterType,result:a.result,stage:a.result==="continued"?"continue":a.result==="reattached"?"reattach":a.result==="resumed"?"resume":a.result==="terminal_reconciled"?"inspect":"terminal",durationMs:a.durationMs,retries:Math.max(0,a.attempts-1),...a.reason?{errorCode:a.reason}:{}})}),this.managerRuntime=new de({getRuntime:()=>this.getRuntime(),getAgentSnapshot:this.getAgentSnapshot??void 0,dispatchReq:a=>this.handleReq(a,{propagateErrors:!0})}),this.chatQueue=new he({getRuntime:()=>this.getRuntime(),dispatchReq:a=>this.handleReq(a),dispatchManagedReq:(a,i)=>Ne(this.getRuntime(),a,i),dispatchQueuedReq:(a,i)=>Ve(this.getRuntime(),a,i),interruptQueuedSession:a=>Le(this.getRuntime(),a)}),this.historySubscriptions=new Pe(this.client),this.nativeCleanupOutbox=new _e({logger:console.info}),this.acpDiscoveryWatcher=new F(async()=>{T(),await E(this.getRuntime())}),this.nativeFusion?.setExternalTerminalHandler?.(a=>{this.handleExternalTerminal(a)}),this.nativeFusion?.setExternalClaimHandler?.((a,i)=>{this.chatQueue.noteExternalClaim(a,i)}),this.nativeCleanupOutbox.start(),v(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents();try{this.acpDiscoveryWatcher.start()}catch(a){console.warn(`[acp.discovery] watcher unavailable: ${a instanceof Error?a.message:String(a)}`)}this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(a=>{console.error("[session.activity] managed probe failed",a)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const a=new Set([...m().map(i=>i.workDir),...Array.from(this.pendingTransfers.values()).map(i=>i.rootPath)]);for(const i of a)try{Ce(this.resolvePath(i))}catch(l){console.error("[attachment.cleanup] failed",l instanceof Error?l.message:String(l))}},6e4),this.attachmentCleanupTimer.unref?.()}executeManagedManagerAction(e){return this.managerRuntime.executeScopedWorkerAction(e.managerSessionId,e.action,e.params,e.roomOrigin)}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,s)=>this.resolveAuthorizedPath(e,s),resolveAuthorizedPathAsync:(e,s)=>this.resolveAuthorizedPathAsync(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor,runJournal:this.runJournal,recoveryBarrier:this.recoveryBarrier,prepareManagedRecovery:this.prepareManagedRecovery}}async resumePendingAcpAuthentication(e){const s=[...this.sessions.values()].find(t=>t.agentType===e&&t.adapter.hasPendingAuthenticationRetry?.());return s?.adapter.resumeAfterExternalAuthentication?(await s.adapter.resumeAfterExternalAuthentication(),!0):!1}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}}prepareForUpgrade(e){this.recoveryBarrier.freezeDispatch();const s=this.runJournal.markPlannedUpgrade(e);return this.runJournal.sync(),{activeRunCount:s}}cancelUpgradePreparation(){this.recoveryBarrier.unfreezeDispatch()}async recoverInterruptedRuns(){this.recoveryPass??=this.recoveryCoordinator.recoverAll(),await this.recoveryPass}getPendingRecoveryCount(){return this.runJournal.loadPendingReconciliation().records.length}revokeProjectFsHandles(){this.projectFsHandleStore.revokeAll()}async commitRecoveryTerminal(e,s){const t=this.sessions.get(e.sessionId);t&&(t.currentRunId=null,t.currentRunPhase=null,t.journalRunId=null,t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null)),this.runTextAcc.delete(`${e.sessionId}:${e.providerRunId??e.runId}`),this.publishSessionActivity(e.sessionId,null);const r=Ye();Ie(e.sessionId,{id:`upgrade-recovery-terminal-${e.runId}`,sessionId:e.sessionId,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:"error",content:r,error:!0,recoveryState:s.result,recoveryReason:s.reason})}),ke({sessionId:e.sessionId,agentType:e.agentType,sessionMode:e.sessionMode,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,status:"failed",attentionId:`upgrade-recovery:${e.runId}`,lastActivityAt:new Date().toISOString()}),this.managerRuntime.noteAgentEvent(e.sessionId,{state:"error",runId:e.providerRunId??e.runId,seq:e.lastObservedEventSeq+1,message:r}),Ae(this.client);const n=e.lastObservedEventSeq+1;this.client.sendAgentEvent({type:"event",event:"agent",payload:s.result==="interrupted_by_upgrade"?{state:"interrupted_by_upgrade",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:n,reason:s.reason}:{state:"recovery_failed",sessionId:e.sessionId,runId:e.providerRunId??e.runId,seq:n,reason:s.reason,...s.message?{message:s.message}:{}},seq:n,id:`agent-recovery-terminal-${e.runId}`}),this.chatQueue.noteTerminal(e.sessionId)}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}setManagedAgentDirectTurnPreparer(e){this.chatQueue.setManagedAgentDirectTurnPreparer(e)}setManagedRecoveryPreparer(e){this.prepareManagedRecovery=e}async enqueueManagedRoomBatch(e){const s=e.map(t=>{const r=m().find(n=>n.sessionId===t.params.sessionId);return{context:t.context,params:{...t.params,modelId:r?.modelId??t.params.modelId,sessionMode:r?.sessionMode??t.params.sessionMode,managerConfig:r?.managerConfig??t.params.managerConfig,managerDefaultWorkerAgentType:r?.managerDefaultWorkerAgentType??t.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:r?.managerDefaultWorkerModelId??t.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(s)}async retireManagedRoomSession(e){this.chatQueue.purgeSession(e),await be(this.getRuntime(),e)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}async synchronizeSessionDeletions(){Ee();try{await this.synchronizeSessionDeletionsOnce(),Fe()}catch(e){if(e instanceof Oe||e instanceof Qe)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)?"transport_unavailable":/invalid (?:deletion sync|session deletion)|deletion revision gap/i.test(s)?"invalid_deletion_sync_response":"deletion_sync_failed";throw console.warn("[session.deletions] retrying failed synchronization",{code:t}),p({code:t,retryable:!0}),e}}async synchronizeSessionDeletionsOnce(){const e=u(),s=xe(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${h.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:g,dataEpoch:f,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const o=t.rejection;throw o?(p({code:o.code,retryable:o.retryable&&o.requiredAction==="retry"}),new Error(o.code)):(p({code:"unstructured_rejection",retryable:!0}),new Error(t.error??"unstructured_rejection"))}const r=t.payload;c(r),Je(r,s);let n=r.baselineRevision;n>s&&y(n);for(const o of r.deletions)o.deletionRevision<=n||(await this.applySessionDeletion(o),n=o.deletionRevision,y(n));if(n!==r.latestRevision)throw new Error(`deletion revision gap: applied ${n}, latest ${r.latestRevision}`);if(n===0)return;const d=await this.client.sendReq({type:"req",id:`session-deletions-ack-${h.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:g,dataEpoch:f,daemonInstallationId:e,deletionRevision:n}});if(!d.ok)throw new Error(d.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new Me({daemonInstallationId:u()});s.recordSession(e);for(const r of e.sources){const n=s.find(r.agentType,r.sourceSessionKey);n&&n.sessionId!==e.sessionId||s.record({...e,...r})}this.runJournal.markSessionAborted(e.sessionId),this.recoveryBarrier.end(e.sessionId);const t=this.sessions.get(e.sessionId);if(t){this.revokeManagedRoomToken(t);try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),De(e.sessionId),Se(e.sessionId),we(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&&(s.journalRunId&&(this.recoveryCoordinator.completeFromProvider(e,s.journalRunId),s.journalRunId=null),this.sessions.delete(e),this.revokeManagedRoomToken(s),s.heartbeatTimer&&clearInterval(s.heartbeatTimer),s.adapter.removeAllListeners(),await s.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const r=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:r,canStop:t.canStop??!0});const n=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:n,runPhase:t.runPhase,canStop:t.canStop??!0},seq:n,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of I())s.startsWith("custom:")&&A(s);const e=P();for(const[s,t]of Object.entries(e.customAgents??{}))ve(s,t)}async handleReq(e,s={}){const t=this.getRuntime();let r;this.activeRequests++;try{switch(e.method){case"schedule.deliver":if(!this.scheduleHandler)throw new Error("schedules_unavailable");await this.scheduleHandler(e);break;case"chat.send":if(e.params.scheduledDelivery)throw new Error("schedule_source_untrusted");c(e.params),this.recoveryBarrier.isDispatchFrozen()||this.recoveryBarrier.isRecovering(String(e.params.sessionId??""))?r={durableQueueHandoff:await this.chatQueue.handleEnqueue({...e,method:"chat.enqueue"})}:await z(t,e);break;case"chat.enqueue":c(e.params),r={durableQueueHandoff: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.queue.promote":c(e.params),await this.chatQueue.handlePromote(e);break;case"chat.abort":c(e.params),await U(t,e);break;case"session.refresh":c(e.params),await H(t,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":N(t,e);break;case"session.tool.detail":await $(t,e);break;case"voice.session.body.window":L(t,e);break;case"session.title.set":await J(t,e);break;case"room.agent.bind-managed":await ye(t,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await Re(t,this.managedRoomBinder,e);break;case"fs.ls":await K(t,e);break;case"project.fs.open":case"project.fs.list":case"project.fs.read":case"project.fs.close":await We(this.client,this.projectFsHandleStore,e,this.trustedProjectFsRequestResolver);break;case"fs.read":await X(t,e);break;case"fs.write":await q(t,e);break;case"fs.export.markdown-pdf":await ne(t,e);break;case"fs.export.markdown-pdf.setup":await re(t,e);break;case"fs.archive.zip":await ie(t,e);break;case"fs.rename":await Z(t,e);break;case"fs.transfer.start":await ae(t,e);break;case"fs.transfer.chunk":await te(t,e);break;case"fs.transfer.finish":await se(t,e);break;case"fs.transfer.abort":await ee(t,e);break;case"region.probe":await oe(t,e);break;case"region.switch":await ce(t,e);break;case"upgrade.start":await C(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0,prepareForUpgrade:n=>this.prepareForUpgrade(n),cancelUpgradePreparation:()=>this.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()}});break;case"upgrade.status":await M(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await le(t,e);break;case"machine.remote-access.pause":await G(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await D(t,e);break;case"agent.permission.respond":await He(t,e);break;case"agent.auth.start":case"agent.auth.logout":case"agent.session-options.get":case"agent.external-sessions.list":case"agent.external-sessions.import":case"agent.external-sessions.close":case"agent.external-sessions.delete":await $e(t,e);break;case"models.refresh":await x(t,e);break;case"agent.config.get":await _(t,e);break;case"agent.config.upsert":await B(t,e);break;case"agent.config.clear":await Q(t,e);break;case"agent.config.test":await j(t,e);break;case"agent.workspace.validate":await V(t,e);break;case"agent.capabilities.list":await O(t,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(n){if(s.propagateErrors)throw n;this.client.sendRes({type:"res",id:e.id,ok:!1,error:n instanceof Error?n.message:String(n)})}finally{this.activeRequests--}return r}evictIdleSessions(){if(this.sessions.size<Ge)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});e&&(this.revokeManagedRoomToken(e.session),e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return fe(e)}resolveAuthorizedPath(e,s){return pe(e,ue(s))}async resolveAuthorizedPathAsync(e,s){return ge(e,await me(s))}async cleanup(){this.chatQueue.stop(),this.projectFsHandleStore.revokeAll(),this.acpDiscoveryWatcher.stop(),this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),Y(this.getRuntime()),await this.managerRuntime.stop(),v(null)}revokeManagedRoomToken(e){const s=e.managedRoomContextToken;e.managedRoomContextToken=null,s&&this.managedRoomContextRevoker?.(s)}}export{Kt as SessionManager,qt as resolveSessionWorkDir,Ye as upgradeRecoveryVisibleMessage};
@@ -35,6 +35,8 @@ export type RoomSummary = {
35
35
  humanCount: number;
36
36
  agentCount: number;
37
37
  lastSequence: RoomSequence;
38
+ /** First sequence still visible to this account. Older servers may omit it. */
39
+ historyFromSequence?: RoomSequence;
38
40
  lastReadSequence?: RoomSequence;
39
41
  unreadCount?: number;
40
42
  membershipRole?: RoomMembershipRole;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.4.76",
3
+ "version": "0.4.78",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {