shennian 0.4.71 → 0.4.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{registerScheduleCommand as ve}from"./commands/schedule.js";import{resolvePersonalScheduleContext as Ie}from"./schedules/personal-context.js";import{executeServerSchedule as Z,deliverServerSchedule as Se}from"./schedules/server-client.js";import{Command as ye}from"commander";import P from"chalk";import we from"node:os";import j from"node:fs";import{loadConfig as m,saveConfig as Ce,getConfigPath as be,getShennianDir as ke,resolveShennianPath as H,normalizeAutoUpgradePolicy as Re}from"./config/index.js";import{CliRelayClient as Pe}from"./relay/client.js";import{collectKnownLogSecrets as X,installCliLogRedaction as Ae,sanitizeCliLogValue as Ee}from"./log-redaction.js";import{registerPairCommand as Te,runSmartStart as De}from"./commands/pair.js";import{registerLogoutCommand as Ue}from"./commands/logout.js";import{clearDaemonPidIfOwner as ee,createDaemonInstanceId as Me,clearDaemonLauncher as oe,findRunningDaemonProcessIds as _e,isRemoteAccessDisabled as ne,prepareRemoteAccessPause as xe,stopPreparedRemoteAccessService as $e,isIsolatedDaemonE2eInstance as Ne,getDaemonStatus as te,registerDaemonCommand as Oe,writeDaemonPid as re,writeDaemonLauncher as ae}from"./commands/daemon.js";import{registerRuntimeCommand as Ve}from"./commands/runtime.js";import{registerAgentCommand as Le}from"./commands/agent.js";import{registerManagerCommand as je}from"./commands/manager.js";import{handleUpgradeStart as He,registerUpgradeCommand as Be}from"./commands/upgrade.js";import{registerToolsCommand as Fe}from"./commands/tools.js";import{registerIntegrationCommand as Ge}from"./commands/integration.js";import{registerRoomCommand as qe}from"./commands/room.js";import{SessionManager as ze}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as Je}from"./session/handlers/project-fs.js";import{SERVERS as B,regionToUrl as Ke,urlToRegion as We}from"./region.js";import{clearUpgradeAttempt as Ye,getCurrentVersion as ie,handleStartupCrashCheck as Qe,readUpgradeAttempt as Ze}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Xe}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as eo}from"./upgrade/updater-bootstrap.js";import{isDesktopDistribution as oo}from"./runtime-distribution.js";import{launchUpdater as no,markUpdaterHealthy as to}from"./upgrade/updater-launcher.js";import{detectAgents as ro}from"./agents/detect.js";import{augmentProcessPath as se}from"./env-path.js";import{acquireDaemonInstanceLock as ao}from"./commands/daemon-instance-lock.js";se();const F=ie();import{getCachedAgentInfos as io,resolveAgentInfos as so}from"./agents/model-registry.js";import{initCliLogReporter as co,reportLog as A,reportUpgradeAggregateMetric as lo}from"./log-reporter.js";import{isNativeFusionEnabled as mo}from"./native-fusion/config.js";import{NativeSessionFusionService as po}from"./native-fusion/service.js";import{startDaemonLogRetention as go}from"./daemon-log.js";import{startDaemonIpcServer as ce}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as uo,resolveManagedRoomIpcPaths as fo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as ho}from"./daemon-ipc/control.js";import{createAgentReadinessHandlers as vo}from"./daemon-ipc/agent-readiness.js";import{scanMachineAgents as Io}from"./session/handlers/agents.js";import{validateAgentWorkspaceConfiguration as So}from"./session/handlers/agent-workspace-validation.js";import{DAEMON_IPC_VERSION as yo}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as wo}from"./room/session-registry.js";import{HttpRoomBindingClient as Co}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as bo}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as ko}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Ro}from"./room/mirror-sync-service.js";import{NativeSourceDeletionStore as Po}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as Ao,isCurrentRegistration as Eo}from"./room/registration-authority.js";import{RoomActivationService as To,resolveManagedAgentPolicy as Do}from"./room/activation-service.js";import{RoomActivationAudit as Uo}from"./room/activation-audit.js";import{RoomAttachmentCache as Mo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as _o}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as xo}from"./room/managed-context.js";import{createManagedRoomExecutor as $o}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as de,SESSION_SYNC_ADVERTISED_CAPABILITIES as le}from"@shennian/wire";import{getDaemonInstallationId as No}from"./session/store.js";import{synchronizeMachinePersonalAuthority as Oo}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as me,ensurePersonalDataEpoch as Vo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Lo,DeletionSyncTerminalError as jo}from"./session/deletion-sync-state.js";const pe=ke(),Ho=H("daemon.pid"),Bo=H("daemon.instance.lock");function Fo(){try{const a=j.readFileSync(Ho,"utf-8").trim();if(a.startsWith("{")){const p=JSON.parse(a),c=Number(p.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 Go(a){return a.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function G(){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 qo(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 ye;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(F),i.enablePositionalOptions(),Ge(i),qe(i),ve(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",we.hostname()).action(async a=>{const s=m(),p=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await De(p,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=H("env.json");try{const e=JSON.parse(j.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);se()}catch{}Ae(X(process.env,m()));const p=go();ne()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(p),process.exit(0));const c=Me(),y=ao({lockPath:Bo,instanceId:c});y.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(y.ownerPid?` (PID ${y.ownerPid})`:"")+", skipping duplicate start"),clearInterval(p),process.exit(0));try{const e=Fo();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 q=Ne(process.env,pe)?[]:_e(process.pid);q.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${q[0]}), skipping duplicate start`),y.release(),process.exit(0)),re(process.pid,c,{version:F}),ae(process.pid,void 0,c),process.on("exit",()=>{ee(process.pid,c),oe(c)}),process.on("exit",y.release);let $=null;try{oo()||($=eo())}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const E=await Qe();if(E){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{no({fromVersion:F,targetVersion:E.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:E.jobId},{readiness:$}),console.error(`[upgrade] New version crashed ${E.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{Vo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const l=m(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??l.serverUrl??B.cn.url,f=ie(),v=await ro({serverUrl:g,authToken:l.machineToken??l.accessToken}),w=v.map(e=>e.type),N=io(v),u=new wo,T=new ko,C=new xo(u),I=new Co(g),ue=new Mo({actionClient:I}),fe=new _o({actionClient:I});let S=null,r;const z=$o({authority:C,actionClient:I,attachmentCache:ue,attachmentUploader:fe,observations:T,getMachineCredentials:()=>({machineToken:m().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),scheduleExecutor:(e,n,o)=>Z({serverUrl:g,machineToken:m().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=u.getByRegistryKey(e)?.binding;t&&u.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 O=null,V=null,L=null;const b=await ce({paths:uo(),handlers:{...ho({daemonVersion:f,protocolVersion:yo,getStatus:()=>te(),getAgents:()=>v,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!O)throw new Error("upgrade_unavailable");return O(e)},prepareExternalUpgrade:e=>{if(!V)throw new Error("external_upgrade_unavailable");return V(e)},cancelExternalUpgrade:()=>{if(!L)throw new Error("external_upgrade_unavailable");return L()}}),...vo({getStatus:()=>te(),discover:async()=>{if(!r)throw new Error("daemon_starting");const e=await Io(!0);return v.splice(0,v.length,...e.detectedAgents),d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:e.agentList,agents:e.agents}}),e.agents.map(n=>{const o=e.detectedAgents.find(t=>t.type===n.type);return{...n,runtimePlatform:o?.runtimePlatform,runtimeIsWsl:o?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return So(e,{ignoreNativeSession:(n,o)=>R?.ignoreNativeSource(n,o)})}})}});let k;try{k=await ce({paths:fo(),handlers:{"managed-room.execute":e=>z(e),"schedule.personal":async e=>{const n=e,o=Ie(n?.context);if(u.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await Z({serverUrl:g,machineToken:m().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let J=!1;const D=()=>{J||(J=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",D),process.once("SIGTERM",D),!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",D),process.removeListener("SIGTERM",D);const K=`${Go(g)}/relay/machine`,U=l.machineToken;if(!U)return;try{await Oo({serverUrl:g,machineToken:U,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&&co(g,l.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${K}... (v${f}) agents: ${w.join(",")}`),A({level:"info",wsEvent:"daemon.start",metadata:{version:f,agents:w}});let R=null,M=null,_=0,h=Ze();const W=Re(l.autoUpgrade),d=new Pe({serverUrl:K,machineToken:U,cliVersion:f,agentList:w,autoUpgradePolicy:W,sessionSyncProtocolVersion:de,sessionSyncCapabilities:le,daemonInstallationId:No(),dataEpoch:me,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),A({level:"info",wsEvent:"daemon.connected"}),N.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:N}}),so(v,{serverUrl:l.serverUrl??g,authToken:l.machineToken??l.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(N)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:o}})}).catch(()=>{}),h&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:h.from,to:h.to,...h.jobId?{jobId:h.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),x.start(),M?.start();const e=++_,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),h){const o=h;h=null,o.jobId&&to(o.jobId,o.to),Ye(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),lo({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===_&&d.getState()==="connected"&&R?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===_&&d.getState()==="connected"){if(o instanceof jo)return;const t=o instanceof Lo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{_+=1,r?.revokeProjectFsHandles(),M?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${qo(e)}`),A({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}`),A({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),r.handleReq(e)}});R=mo()?new po(d):null,M=new Ro({registry:u,observations:T,actionClient:I,getMachineCredentials:()=>({machineToken:m().machineToken}),isSessionDeleted:e=>new Po().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 x=new Xe({currentVersion:f,machineId:l.machineId??"unknown-machine",serverUrl:l.serverUrl??g,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:de,dataEpoch:me,advertisedCapabilities:le,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(d,"auto-upgrade",e,{currentVersion:f,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})}});x.setPolicy(W),r=new ze(d,R,f,x,new bo({registry:u,observations:T,bindingClient:I,getMachineCredentials:()=>{const e=m();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>xe(),isDisabled:()=>ne(),shutdownAfterResponse:()=>{setTimeout(()=>{$e(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>C.revokeToken(e),z,null,Je(U)),O=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 He(d,`local-upgrade-${Date.now()}`,o,{currentVersion:f,confirmedMajor:t,prepareForUpgrade:he=>r.prepareForUpgrade(he),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},V=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)}},L=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),S=new To({registry:u,observations:T,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=m().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(!Eo(u,e)||!Ao(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=m().machineToken;if(!t)throw new Error("machine_not_authorized");return C.issue({registryKey:e,machineCredential:t,activationCauseMessageId:n,allowedActions:o})},audit:new Uo,resolveManagedAgentPolicy:e=>Do({...e,runtime:v.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>Se({serverUrl:g,machineToken:m().machineToken,req:e,client:d,queue:r.getScheduleQueue(),registry:u}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(u.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await I.status({machineToken:m().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}])),j.mkdirSync(pe,{recursive:!0}),re(process.pid,c,{version:f}),ae(process.pid,void 0,c),d.connect(),process.stdin.resume();let Y=!1;const Q=async()=>{if(!Y){Y=!0,console.log(P.gray(`
3
- Disconnecting...`)),A({level:"info",wsEvent:"daemon.stop"}),clearInterval(p);try{await r.cleanup(),x.stop(),R?.stop(),M?.stop(),d.disconnect(),await Promise.allSettled([b.close(),k.close()]),ee(process.pid,c),oe(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{Q()}),process.on("SIGTERM",()=>{Q()})}),i.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>G()),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(()=>G()),i.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>G());const ge=i.command("config").description("View or modify configuration");ge.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${be()}`);const a=m(),s=Ee(a,X(process.env,a));if(a.serverUrl){const p=We(a.serverUrl);console.log(`Region: ${p} (${B[p].label})`)}console.log(JSON.stringify(s,null,2))}),ge.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 p=m();if(a==="server"){s!=="cn"&&s!=="global"&&(console.error(P.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=s;p.serverUrl=Ke(c),Ce(p),console.log(P.green(`\u2713 Server set to ${B[c].label}`)),console.log(P.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(P.red(`\u2717 Unknown config key: ${a}. Supported: server`)),process.exit(1)}),Te(i),Ue(i),Oe(i),Ve(i),Le(i),je(i),Be(i),Fe(i),i.parse();
1
+ import{registerScheduleCommand as ve}from"./commands/schedule.js";import{resolvePersonalScheduleContext as Ie}from"./schedules/personal-context.js";import{executeServerSchedule as Z,deliverServerSchedule as Se}from"./schedules/server-client.js";import{Command as ye}from"commander";import P from"chalk";import we from"node:os";import j from"node:fs";import{loadConfig as m,saveConfig as Ce,getConfigPath as be,getShennianDir as ke,resolveShennianPath as H,normalizeAutoUpgradePolicy as Re}from"./config/index.js";import{CliRelayClient as Pe}from"./relay/client.js";import{collectKnownLogSecrets as X,installCliLogRedaction as Ae,sanitizeCliLogValue as Ee}from"./log-redaction.js";import{registerPairCommand as Te,runSmartStart as De}from"./commands/pair.js";import{registerLogoutCommand as Ue}from"./commands/logout.js";import{clearDaemonPidIfOwner as ee,createDaemonInstanceId as Me,clearDaemonLauncher as oe,findRunningDaemonProcessIds as _e,isRemoteAccessDisabled as ne,prepareRemoteAccessPause as xe,stopPreparedRemoteAccessService as $e,isIsolatedDaemonE2eInstance as Ne,getDaemonStatus as te,registerDaemonCommand as Oe,writeDaemonPid as re,writeDaemonLauncher as ae}from"./commands/daemon.js";import{registerRuntimeCommand as Ve}from"./commands/runtime.js";import{registerAgentCommand as Le}from"./commands/agent.js";import{registerManagerCommand as je}from"./commands/manager.js";import{registerWorkerCommand as He}from"./commands/worker.js";import{handleUpgradeStart as Be,registerUpgradeCommand as Fe}from"./commands/upgrade.js";import{registerToolsCommand as Ge}from"./commands/tools.js";import{registerIntegrationCommand as qe}from"./commands/integration.js";import{registerRoomCommand as ze}from"./commands/room.js";import{SessionManager as We}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as Je}from"./session/handlers/project-fs.js";import{SERVERS as B,regionToUrl as Ke,urlToRegion as Ye}from"./region.js";import{clearUpgradeAttempt as Qe,getCurrentVersion as ie,handleStartupCrashCheck as Ze,readUpgradeAttempt as Xe}from"./upgrade/engine.js";import{AutoUpgradeScheduler as eo}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as oo}from"./upgrade/updater-bootstrap.js";import{isDesktopDistribution as no}from"./runtime-distribution.js";import{launchUpdater as to,markUpdaterHealthy as ro}from"./upgrade/updater-launcher.js";import{detectAgents as ao}from"./agents/detect.js";import{augmentProcessPath as se}from"./env-path.js";import{acquireDaemonInstanceLock as io}from"./commands/daemon-instance-lock.js";se();const F=ie();import{getCachedAgentInfos as so,resolveAgentInfos as co}from"./agents/model-registry.js";import{initCliLogReporter as lo,reportLog as A,reportUpgradeAggregateMetric as mo}from"./log-reporter.js";import{isNativeFusionEnabled as po}from"./native-fusion/config.js";import{NativeSessionFusionService as go}from"./native-fusion/service.js";import{startDaemonLogRetention as uo}from"./daemon-log.js";import{startDaemonIpcServer as ce}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as fo,resolveManagedRoomIpcPaths as ho}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as vo}from"./daemon-ipc/control.js";import{createAgentReadinessHandlers as Io}from"./daemon-ipc/agent-readiness.js";import{scanMachineAgents as So}from"./session/handlers/agents.js";import{validateAgentWorkspaceConfiguration as yo}from"./session/handlers/agent-workspace-validation.js";import{DAEMON_IPC_VERSION as wo}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as Co}from"./room/session-registry.js";import{HttpRoomBindingClient as bo}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as ko}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as Ro}from"./room/host-session-observations.js";import{RoomMirrorSyncService as Po}from"./room/mirror-sync-service.js";import{NativeSourceDeletionStore as Ao}from"./native-fusion/native-source-deletions.js";import{matchesRoomOwner as Eo,isCurrentRegistration as To}from"./room/registration-authority.js";import{RoomActivationService as Do,resolveManagedAgentPolicy as Uo}from"./room/activation-service.js";import{RoomActivationAudit as Mo}from"./room/activation-audit.js";import{RoomAttachmentCache as _o}from"./room/attachment-cache.js";import{RoomAttachmentUploader as xo}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as $o}from"./room/managed-context.js";import{createManagedRoomExecutor as No}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as de,SESSION_SYNC_ADVERTISED_CAPABILITIES as le}from"@shennian/wire";import{getDaemonInstallationId as Oo}from"./session/store.js";import{synchronizeMachinePersonalAuthority as Vo}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as me,ensurePersonalDataEpoch as Lo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as jo,DeletionSyncTerminalError as Ho}from"./session/deletion-sync-state.js";const pe=ke(),Bo=H("daemon.pid"),Fo=H("daemon.instance.lock");function Go(){try{const a=j.readFileSync(Bo,"utf-8").trim();if(a.startsWith("{")){const p=JSON.parse(a),c=Number(p.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 G(){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 ye;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(F),i.enablePositionalOptions(),qe(i),ze(i),ve(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",we.hostname()).action(async a=>{const s=m(),p=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await De(p,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=H("env.json");try{const e=JSON.parse(j.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);se()}catch{}Ae(X(process.env,m()));const p=uo();ne()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(p),process.exit(0));const c=Me(),y=io({lockPath:Fo,instanceId:c});y.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(y.ownerPid?` (PID ${y.ownerPid})`:"")+", skipping duplicate start"),clearInterval(p),process.exit(0));try{const e=Go();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 q=Ne(process.env,pe)?[]:_e(process.pid);q.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${q[0]}), skipping duplicate start`),y.release(),process.exit(0)),re(process.pid,c,{version:F}),ae(process.pid,void 0,c),process.on("exit",()=>{ee(process.pid,c),oe(c)}),process.on("exit",y.release);let $=null;try{no()||($=oo())}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const E=await Ze();if(E){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{to({fromVersion:F,targetVersion:E.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:E.jobId},{readiness:$}),console.error(`[upgrade] New version crashed ${E.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{Lo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const l=m(),g=a.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??l.serverUrl??B.cn.url,f=ie(),v=await ao({serverUrl:g,authToken:l.machineToken??l.accessToken}),w=v.map(e=>e.type),N=so(v),u=new Co,T=new Ro,C=new $o(u),I=new bo(g),ue=new _o({actionClient:I}),fe=new xo({actionClient:I});let S=null,r;const z=No({authority:C,actionClient:I,attachmentCache:ue,attachmentUploader:fe,observations:T,getMachineCredentials:()=>({machineToken:m().machineToken}),managerExecutor:e=>r.executeManagedManagerAction(e),scheduleExecutor:(e,n,o)=>Z({serverUrl:g,machineToken:m().machineToken,sessionId:e,action:n,args:o}),onPaused:({registryKey:e,sessionId:n})=>{const t=u.getByRegistryKey(e)?.binding;t&&u.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 O=null,V=null,L=null;const b=await ce({paths:fo(),handlers:{...vo({daemonVersion:f,protocolVersion:wo,getStatus:()=>te(),getAgents:()=>v,completeAgentAuthentication:e=>r.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!O)throw new Error("upgrade_unavailable");return O(e)},prepareExternalUpgrade:e=>{if(!V)throw new Error("external_upgrade_unavailable");return V(e)},cancelExternalUpgrade:()=>{if(!L)throw new Error("external_upgrade_unavailable");return L()}}),...Io({getStatus:()=>te(),discover:async()=>{if(!r)throw new Error("daemon_starting");const e=await So(!0);return v.splice(0,v.length,...e.detectedAgents),d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:e.agentList,agents:e.agents}}),e.agents.map(n=>{const o=e.detectedAgents.find(t=>t.type===n.type);return{...n,runtimePlatform:o?.runtimePlatform,runtimeIsWsl:o?.runtimeIsWsl}})},validate:async e=>{if(!r)throw new Error("daemon_starting");return yo(e,{ignoreNativeSession:(n,o)=>R?.ignoreNativeSource(n,o)})}})}});let k;try{k=await ce({paths:ho(),handlers:{"managed-room.execute":e=>z(e),"schedule.personal":async e=>{const n=e,o=Ie(n?.context);if(u.getByNianSessionId(o)?.binding)throw new Error("schedule_personal_context_required");return await Z({serverUrl:g,machineToken:m().machineToken,sessionId:o,action:n.operation,args:n})}}})}catch(e){throw await b.close(),e}let W=!1;const D=()=>{W||(W=!0,Promise.allSettled([b.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",D),process.once("SIGTERM",D),!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",D),process.removeListener("SIGTERM",D);const J=`${qo(g)}/relay/machine`,U=l.machineToken;if(!U)return;try{await Vo({serverUrl:g,machineToken:U,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&&lo(g,l.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${J}... (v${f}) agents: ${w.join(",")}`),A({level:"info",wsEvent:"daemon.start",metadata:{version:f,agents:w}});let R=null,M=null,_=0,h=Xe();const K=Re(l.autoUpgrade),d=new Pe({serverUrl:J,machineToken:U,cliVersion:f,agentList:w,autoUpgradePolicy:K,sessionSyncProtocolVersion:de,sessionSyncCapabilities:le,daemonInstallationId:Oo(),dataEpoch:me,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),A({level:"info",wsEvent:"daemon.connected"}),N.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:N}}),co(v,{serverUrl:l.serverUrl??g,authToken:l.machineToken??l.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(N)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:o}})}).catch(()=>{}),h&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:h.from,to:h.to,...h.jobId?{jobId:h.jobId}:{},activeRunCount:r.getPendingRecoveryCount()}}),x.start(),M?.start();const e=++_,n=async()=>{try{if(await r.synchronizeSessionDeletions(),await r.recoverInterruptedRuns(),h){const o=h;h=null,o.jobId&&ro(o.jobId,o.to),Qe(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),mo({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===_&&d.getState()==="connected"&&R?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===_&&d.getState()==="connected"){if(o instanceof Ho)return;const t=o instanceof jo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},t).unref?.()}}};n()},onDisconnected:e=>{_+=1,r?.revokeProjectFsHandles(),M?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${zo(e)}`),A({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}`),A({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),r.handleReq(e)}});R=po()?new go(d):null,M=new Po({registry:u,observations:T,actionClient:I,getMachineCredentials:()=>({machineToken:m().machineToken}),isSessionDeleted:e=>new Ao().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 x=new eo({currentVersion:f,machineId:l.machineId??"unknown-machine",serverUrl:l.serverUrl??g,getIdleState:()=>r.getUpgradeIdleState(),sessionSyncProtocolVersion:de,dataEpoch:me,advertisedCapabilities:le,updaterProtocolVersion:1,startUpgrade:async(e,n)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(d,"auto-upgrade",e,{currentVersion:f,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})}});x.setPolicy(K),r=new We(d,R,f,x,new ko({registry:u,observations:T,bindingClient:I,getMachineCredentials:()=>{const e=m();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>xe(),isDisabled:()=>ne(),shutdownAfterResponse:()=>{setTimeout(()=>{$e(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>C.revokeToken(e),z,null,Je(U)),O=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 Be(d,`local-upgrade-${Date.now()}`,o,{currentVersion:f,confirmedMajor:t,prepareForUpgrade:he=>r.prepareForUpgrade(he),cancelUpgradePreparation:()=>r.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},V=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)}},L=()=>(r.cancelUpgradePreparation(),{cancelled:!0}),S=new Do({registry:u,observations:T,enqueueConversationBatch:e=>r.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=m().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(!To(u,e)||!Eo(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=m().machineToken;if(!t)throw new Error("machine_not_authorized");return C.issue({registryKey:e,machineCredential:t,activationCauseMessageId:n,allowedActions:o})},audit:new Mo,resolveManagedAgentPolicy:e=>Uo({...e,runtime:v.find(n=>n.type===e.registration.runtimeAdapter)})}),r.scheduleHandler=e=>Se({serverUrl:g,machineToken:m().machineToken,req:e,client:d,queue:r.getScheduleQueue(),registry:u}),r.getScheduleQueue().validateScheduledTarget=async e=>{if(!e.bindingId||!e.roomId)return"ready";if(u.getByNianSessionId(e.sessionId)?.binding?.bindingId!==e.bindingId)return"ended";const o=await I.status({machineToken:m().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}])),j.mkdirSync(pe,{recursive:!0}),re(process.pid,c,{version:f}),ae(process.pid,void 0,c),d.connect(),process.stdin.resume();let Y=!1;const Q=async()=>{if(!Y){Y=!0,console.log(P.gray(`
3
+ Disconnecting...`)),A({level:"info",wsEvent:"daemon.stop"}),clearInterval(p);try{await r.cleanup(),x.stop(),R?.stop(),M?.stop(),d.disconnect(),await Promise.allSettled([b.close(),k.close()]),ee(process.pid,c),oe(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{Q()}),process.on("SIGTERM",()=>{Q()})}),i.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>G()),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(()=>G()),i.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>G());const ge=i.command("config").description("View or modify configuration");ge.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${be()}`);const a=m(),s=Ee(a,X(process.env,a));if(a.serverUrl){const p=Ye(a.serverUrl);console.log(`Region: ${p} (${B[p].label})`)}console.log(JSON.stringify(s,null,2))}),ge.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 p=m();if(a==="server"){s!=="cn"&&s!=="global"&&(console.error(P.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=s;p.serverUrl=Ke(c),Ce(p),console.log(P.green(`\u2713 Server set to ${B[c].label}`)),console.log(P.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(P.red(`\u2717 Unknown config key: ${a}. Supported: server`)),process.exit(1)}),Te(i),Ue(i),Oe(i),Ve(i),Le(i),je(i),He(i),Fe(i),Ge(i),i.parse();
@@ -1,2 +1,2 @@
1
- export declare const MANAGER_SYSTEM_PROMPT = "\u4F60\u662F\u9879\u76EE\u7ECF\u7406\uFF0C\u662F\u5F53\u524D\u9879\u76EE\u7684\u7BA1\u7406\u8005\u3002\n\n\u4F60\u7684\u804C\u8D23\uFF1A\n- \u7406\u89E3\u7528\u6237\u76EE\u6807\u3002\n- \u62C6\u89E3\u4EFB\u52A1\u3002\n- \u521B\u5EFA\u3001\u6307\u6D3E\u3001\u89C2\u5BDF\u548C\u505C\u6B62\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u4E0B\u7684 worker Agent session\u3002\n- \u6C47\u603B worker \u7ED3\u679C\u3002\n- \u5224\u65AD\u662F\u5426\u9700\u8981\u7EE7\u7EED\u7B49\u5F85\u3001\u8C03\u6574\u5B89\u6392\u3001\u8BE2\u95EE\u7528\u6237\u6216\u9A8C\u6536\u3002\n- \u5728\u9879\u76EE .shennian/ \u76EE\u5F55\u4E0B\u7EF4\u62A4\u5FC5\u8981\u7684\u8BA1\u5212\u3001\u8BB0\u5F55\u548C\u9879\u76EE\u8BB0\u5FC6\u3002\n\n\u4F60\u7684\u8FB9\u754C\uFF1A\n- \u4E0D\u8981\u628A\u81EA\u5DF1\u5F53\u4F5C\u4E3B\u8981\u6267\u884C\u8005\u3002\n- \u4E0D\u8981\u76F4\u63A5\u7F16\u8F91\u4E1A\u52A1\u4EE3\u7801\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u4EB2\u81EA\u6267\u884C\u3002\n- \u53EF\u4EE5\u8BFB\u53D6\u6587\u4EF6\u3001\u641C\u7D22\u9879\u76EE\u548C\u68C0\u67E5\u4E0A\u4E0B\u6587\uFF0C\u4EE5\u4FBF\u505A\u5224\u65AD\u3002\n- \u9700\u8981\u4FEE\u6539\u4EE3\u7801\u3001\u8FD0\u884C\u6D4B\u8BD5\u3001\u8C03\u7814\u65B9\u6848\u65F6\uFF0C\u4F18\u5148\u521B\u5EFA\u6216\u6307\u6D3E worker\u3002\n- \u6BCF\u6B21\u6536\u5230\u65B0\u4EFB\u52A1\u3001\u8865\u5145\u8981\u6C42\u3001\u7EA0\u504F\u6216\u5916\u90E8\u6D88\u606F\u65F6\uFF0C\u5148\u7528 sessions list \u67E5\u770B\u5F53\u524D Manager \u81EA\u5DF1\u521B\u5EFA\u7684 worker\uFF1B\u5982\u679C\u53EF\u80FD\u76F8\u5173\uFF0C\u518D\u7528 sessions read \u8BFB\u53D6\u5FC5\u8981\u6458\u8981\u540E\u5224\u65AD\u662F\u5426\u590D\u7528\u3002\u4E0D\u8981\u5C1D\u8BD5\u53D1\u73B0\u6216\u63A7\u5236\u540C\u9879\u76EE\u4E2D\u5C5E\u4E8E\u65E7 Manager\u3001\u5176\u4ED6 Manager \u6216\u666E\u901A\u4F1A\u8BDD\u7684 Session\u3002\n- \u5982\u679C\u5DF2\u6709 worker \u6B63\u5728\u5904\u7406\u540C\u4E00\u76EE\u6807\u3001\u540C\u4E00\u529F\u80FD\u533A\u3001\u540C\u4E00\u6587\u4EF6\u8303\u56F4\u6216\u540C\u4E00\u95EE\u9898\u94FE\u8DEF\uFF0C\u4F18\u5148\u7528 sessions send \u628A\u65B0\u8981\u6C42\u53D1\u7ED9\u8FD9\u4E2A worker\uFF1B\u5373\u4F7F worker \u6B63\u5FD9\u4E5F\u53EF\u4EE5\u53D1\u9001\uFF0C\u9ED8\u8BA4\u4F1A\u8FDB\u5165\u672C\u673A\u961F\u5217\uFF0C\u4E0D\u8981\u56E0\u4E3A\u5B83\u5FD9\u5C31\u65B0\u5EFA worker\u3002\n- \u53EA\u6709\u6CA1\u6709\u76F8\u5173 worker\u3001\u73B0\u6709 worker \u5DF2\u660E\u663E\u4E0D\u9002\u5408\u7EE7\u7EED\u63A8\u8FDB\u3001\u6216\u4EFB\u52A1\u9700\u8981\u5E76\u884C\u62C6\u5206\u7ED9\u4E0D\u540C\u4E13\u957F\u65F6\uFF0C\u624D\u521B\u5EFA\u65B0\u7684 worker\u3002\n- \u521B\u5EFA\u6216\u6307\u6D3E worker \u540E\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u5F53\u573A\u7EE7\u7EED\u8C03\u5EA6\uFF0C\u5426\u5219\u56DE\u590D\u7528\u6237\u5DF2\u5B89\u6392\u5E76\u7ED3\u675F\u5F53\u524D turn\uFF1B\u4E0D\u8981\u4E3B\u52A8\u8F6E\u8BE2 worker \u72B6\u6001\uFF0C\u795E\u5FF5\u4F1A\u5728 worker \u7EC8\u6001\u6216\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u91CD\u65B0\u5524\u9192\u4F60\u3002\n- \u521B\u5EFA worker \u65F6\u5FC5\u987B\u63D0\u4F9B\u7B80\u77ED\u3001\u5177\u4F53\u3001\u53EF\u533A\u5206\u7684 --title\uFF0C\u540C\u65F6\u628A\u5B8C\u6574\u4EFB\u52A1\u653E\u8FDB --message\uFF1B\u4E0D\u8981\u628A\u6574\u6BB5\u4EFB\u52A1\u6216 Session ID \u5F53\u6807\u9898\u3002\n- sessions read \u8FD4\u56DE\u7684\u662F\u7ED9\u7BA1\u7406\u8005\u770B\u7684\u7B80\u6D01\u8FDB\u5C55\u3001\u5DE5\u5177\u6458\u8981\u548C\u6700\u7EC8\u7ED3\u679C\uFF0C\u4E0D\u662F\u539F\u59CB\u6D41\u5F0F token\uFF1B\u4E0D\u8981\u8981\u6C42\u8BFB\u53D6\u6216\u8F6C\u8FF0\u5B8C\u6574\u6D41\u5F0F\u65E5\u5FD7\u3002\n- \u53EA\u80FD\u7BA1\u7406\u7531\u5F53\u524D Manager \u521B\u5EFA\u3001\u4E14\u4E0E\u4F60\u5904\u4E8E\u540C\u4E00\u53F0\u673A\u5668\u548C\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u7684 worker\uFF1B\u4E0D\u8981\u8DE8 Manager\u3001\u8DE8\u673A\u5668\u6216\u8DE8\u9879\u76EE\u8C03\u5EA6\u3002\n- \u4E0D\u8981\u65E0\u9650\u5FAA\u73AF\uFF1B\u6CA1\u6709\u660E\u786E\u4E0B\u4E00\u6B65\u65F6\u8BE2\u95EE\u7528\u6237\u6216\u7ED3\u675F\u5F53\u524D turn \u7B49\u5F85\u7CFB\u7EDF\u4E8B\u4EF6\u3002\n- \u4E0D\u8981\u81EA\u5DF1\u8BBE\u7F6E\u5B9A\u65F6\u5524\u9192\uFF1B\u795E\u5FF5\u4F1A\u5728\u7528\u6237\u6D88\u606F\u3001worker \u7EC8\u6001\u6216 worker \u957F\u8FD0\u884C\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u5524\u9192\u4F60\u3002\n- \u5916\u90E8\u6D88\u606F\u901A\u9053\u4E8B\u4EF6\u4F1A\u50CF\u666E\u901A\u7528\u6237\u6D88\u606F\u4E00\u6837\u9001\u8FBE\uFF0C\u683C\u5F0F\u4E3A\u201C\u5916\u90E8\u5BF9\u8BDD: <\u5BF9\u8BDD\u540D\u79F0>\u201D\u540E\u8DDF\u201C\u5BF9\u65B9: <\u5185\u5BB9\u6216\u9644\u4EF6\u94FE\u63A5>\u201D\uFF0C\u53EF\u80FD\u662F\u5408\u5E76\u6D88\u606F\uFF0C\u4E5F\u53EF\u80FD\u5305\u542B\u56FE\u7247\u3001\u89C6\u9891\u6216\u6587\u4EF6 URL\u3002\n- \u5916\u90E8\u6D88\u606F\u662F\u5426\u9700\u8981\u56DE\u590D\u3001\u8FFD\u95EE\u3001\u5FFD\u7565\u3001\u8F6C\u4EA4\u5185\u90E8\u8D1F\u8D23\u4EBA\uFF0C\u6216\u521B\u5EFA/\u6307\u6D3E worker\uFF0C\u7531\u4F60\u6839\u636E\u9879\u76EE\u4E0A\u4E0B\u6587\u663E\u5F0F\u5224\u65AD\u3002\n- \u5BF9\u5916\u4F60\u662F\u5F53\u524D\u9879\u76EE\u7684\u9879\u76EE\u7ECF\u7406\uFF0C\u4E0D\u8981\u81EA\u79F0\u795E\u5FF5\u3001Manager Agent \u6216 worker\uFF0C\u4E5F\u4E0D\u8981\u89E3\u91CA\u5185\u90E8\u8C03\u5EA6\u673A\u5236\uFF1B\u53EA\u5728\u9700\u8981\u65F6\u7528\u201C\u6211\u8FD9\u8FB9/\u6211\u4EEC\u8FD9\u8FB9\u201D\u6C9F\u901A\u3002\n- \u4E0D\u8981\u628A\u6240\u6709\u7EC6\u8282\u585E\u8FDB\u5BF9\u8BDD\u4E0A\u4E0B\u6587\uFF1B\u9700\u8981\u957F\u671F\u4FDD\u5B58\u7684\u4FE1\u606F\u5199\u5230\u9879\u76EE .shennian/ \u4E0B\u3002\n\n\u9700\u8981\u7BA1\u7406 worker \u6216\u5916\u90E8\u901A\u9053\u65F6\uFF0C\u4F7F\u7528\u672C\u5730\u547D\u4EE4\uFF1A\n- shennian manager sessions list --json\n- shennian manager sessions start --agent <codex|claude|workbuddy|cursor|opencode|pi-coding-agent|custom:name> --workdir <path> --title <\u77ED\u6807\u9898> --message <\u5B8C\u6574\u4EFB\u52A1>\n- shennian manager sessions send --session-id <id> --message <text>\n- shennian manager sessions queue list --session-id <id> --json\n- shennian manager sessions queue edit --session-id <id> --message-id <queueMessageId> --message <text>\n- shennian manager sessions queue delete --session-id <id> --message-id <queueMessageId>\n- shennian manager sessions stop --session-id <id>\uFF08\u7EC8\u6B62\u6B63\u5728\u8FD0\u884C\u7684 worker\uFF1B\u4E5F\u53EF\u7528 terminate/kill \u522B\u540D\uFF09\n- shennian manager sessions read --session-id <id> --limit 200 --json\n- shennian manager sessions wait --session-id <id> --timeout <1-600>\uFF08\u4EC5\u5916\u90E8\u539F\u751F\u5BBF\u4E3B\u9700\u8981\u6709\u754C\u7B49\u5F85\u65F6\u4F7F\u7528\u4E00\u6B21\uFF1B\u53D7\u7BA1 Manager \u4E0D\u4E3B\u52A8\u8C03\u7528\uFF09\n- shennian manager memory path\n\n\u5982\u679C\u5BBF\u4E3B\u76F4\u63A5\u63D0\u4F9B\u4E86 shennian_manager_* \u5DE5\u5177\uFF0C\u4F18\u5148\u4F7F\u7528\u8FD9\u4E9B\u5DE5\u5177\u5B8C\u6210\u540C\u540D\u64CD\u4F5C\uFF0C\u4E0D\u8981\u518D\u901A\u8FC7 Shell \u8C03\u7528\u547D\u4EE4\u3002\u9650\u5236\u6743\u9650\u53EA\u7EA6\u675F Provider \u81EA\u8EAB\u7684\u6587\u4EF6\u3001Shell\u3001\u7F51\u7EDC\u548C\u666E\u901A\u5DE5\u5177\u80FD\u529B\uFF0C\u4E0D\u4F1A\u64A4\u9500\u795E\u5FF5\u6CE8\u5165\u7684 Manager Worker \u7BA1\u7406\u80FD\u529B\uFF1B\u4E0D\u5F97\u628A\u201C\u9650\u5236\u6743\u9650\u201D\u8BEF\u89E3\u4E3A\u4E0D\u80FD\u521B\u5EFA\u3001\u8BFB\u53D6\u3001\u53D1\u9001\u6216\u505C\u6B62 Worker\u3002\n\n\u53EF\u521B\u5EFA\u7684 worker Agent \u5305\u62EC Codex\u3001Claude Code\u3001WorkBuddy\u3001Cursor\u3001opencode\u3001Pi\uFF0C\u4EE5\u53CA\u672C\u673A custom agent\uFF08custom:<name>\uFF09\u3002sessions send \u59CB\u7EC8\u5148\u628A worker \u6D88\u606F\u4EA4\u7ED9\u672C\u673A durable queue\uFF1Aworker \u6B63\u5FD9\u65F6\u7B49\u5F85\uFF0C\u7A7A\u95F2\u65F6\u7ACB\u5373\u7531\u540C\u4E00\u8C03\u5EA6\u5668\u6267\u884C\u3002\u961F\u5217\u91CC\u7684\u672A\u6267\u884C\u6D88\u606F\u53EF\u4EE5 list/edit/delete\uFF1B\u5DF2\u7ECF\u5F00\u59CB\u6267\u884C\u7684\u6D88\u606F\u4E0D\u80FD\u7F16\u8F91\u6216\u5220\u9664\uFF0C\u53EA\u80FD stop \u540E\u91CD\u65B0\u53D1\u9001\u3002\n\n\u8FD9\u4E9B\u547D\u4EE4\u5DF2\u7ECF\u7531\u795E\u5FF5\u6CE8\u5165\u5F53\u524D Manager \u8EAB\u4EFD\u548C\u540C\u9879\u76EE\u6743\u9650\u8FB9\u754C\u3002\u4E0D\u8981\u5C1D\u8BD5\u4F2A\u9020 Manager session id\u3002";
1
+ export declare const MANAGER_SYSTEM_PROMPT = "\u4F60\u662F\u9879\u76EE\u7ECF\u7406\uFF0C\u662F\u5F53\u524D\u9879\u76EE\u7684\u7BA1\u7406\u8005\u3002\n\n\u4F60\u7684\u804C\u8D23\uFF1A\n- \u7406\u89E3\u7528\u6237\u76EE\u6807\u3002\n- \u62C6\u89E3\u4EFB\u52A1\u3002\n- \u521B\u5EFA\u3001\u6307\u6D3E\u3001\u89C2\u5BDF\u548C\u505C\u6B62\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u4E0B\u7684 worker Agent session\u3002\n- \u6C47\u603B worker \u7ED3\u679C\u3002\n- \u5224\u65AD\u662F\u5426\u9700\u8981\u7EE7\u7EED\u7B49\u5F85\u3001\u8C03\u6574\u5B89\u6392\u3001\u8BE2\u95EE\u7528\u6237\u6216\u9A8C\u6536\u3002\n- \u5728\u9879\u76EE .shennian/ \u76EE\u5F55\u4E0B\u7EF4\u62A4\u5FC5\u8981\u7684\u8BA1\u5212\u3001\u8BB0\u5F55\u548C\u9879\u76EE\u8BB0\u5FC6\u3002\n\n\u4F60\u7684\u8FB9\u754C\uFF1A\n- \u4E0D\u8981\u628A\u81EA\u5DF1\u5F53\u4F5C\u4E3B\u8981\u6267\u884C\u8005\u3002\n- \u4E0D\u8981\u76F4\u63A5\u7F16\u8F91\u4E1A\u52A1\u4EE3\u7801\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u4EB2\u81EA\u6267\u884C\u3002\n- \u53EF\u4EE5\u8BFB\u53D6\u6587\u4EF6\u3001\u641C\u7D22\u9879\u76EE\u548C\u68C0\u67E5\u4E0A\u4E0B\u6587\uFF0C\u4EE5\u4FBF\u505A\u5224\u65AD\u3002\n- \u9700\u8981\u4FEE\u6539\u4EE3\u7801\u3001\u8FD0\u884C\u6D4B\u8BD5\u3001\u8C03\u7814\u65B9\u6848\u65F6\uFF0C\u4F18\u5148\u521B\u5EFA\u6216\u6307\u6D3E worker\u3002\n- \u660E\u786E\u7684\u65B0\u76EE\u6807\u9ED8\u8BA4\u521B\u5EFA\u65B0\u7684 worker\uFF0C\u4E0D\u8981\u4E3A\u4E86\u590D\u7528\u800C\u5148\u679A\u4E3E\u5386\u53F2\uFF0C\u4E5F\u4E0D\u8981\u628A\u4E0D\u540C\u76EE\u6807\u6DF7\u8FDB\u65E7 worker\u3002\n- \u53EA\u6709\u7528\u6237\u660E\u786E\u7EED\u63A5\u3001\u7EA0\u504F\u3001\u8865\u5145\u540C\u4E00\u95EE\u9898\u94FE\u8DEF\uFF0C\u6216\u7CFB\u7EDF\u6B63\u5728\u6062\u590D\u5148\u524D\u5B89\u6392\u65F6\uFF0C\u624D\u7528 sessions list \u67E5\u770B\u5F53\u524D Manager \u81EA\u5DF1\u521B\u5EFA\u7684 worker\uFF0C\u5E76\u7528 sessions read \u6838\u5BF9\u540E\u590D\u7528\u3002\u4E0D\u8981\u5C1D\u8BD5\u53D1\u73B0\u6216\u63A7\u5236\u540C\u9879\u76EE\u4E2D\u5C5E\u4E8E\u65E7 Manager\u3001\u5176\u4ED6 Manager \u6216\u666E\u901A\u4F1A\u8BDD\u7684 Session\u3002\n- \u5982\u679C\u5DF2\u6709 worker \u6B63\u5728\u5904\u7406\u540C\u4E00\u76EE\u6807\uFF0C\u4F18\u5148\u7528 sessions send \u628A\u8865\u5145\u8981\u6C42\u53D1\u7ED9\u8FD9\u4E2A worker\uFF1B\u5373\u4F7F worker \u6B63\u5FD9\u4E5F\u53EF\u4EE5\u53D1\u9001\uFF0C\u9ED8\u8BA4\u4F1A\u8FDB\u5165\u672C\u673A\u961F\u5217\uFF0C\u4E0D\u8981\u56E0\u4E3A\u5B83\u5FD9\u5C31\u91CD\u590D\u521B\u5EFA\u3002\n- \u521B\u5EFA\u6216\u6307\u6D3E worker \u540E\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u5F53\u573A\u7EE7\u7EED\u8C03\u5EA6\uFF0C\u5426\u5219\u56DE\u590D\u7528\u6237\u5DF2\u5B89\u6392\u5E76\u7ED3\u675F\u5F53\u524D turn\uFF1B\u4E0D\u8981\u4E3B\u52A8\u8F6E\u8BE2 worker \u72B6\u6001\uFF0C\u795E\u5FF5\u4F1A\u5728 worker \u7EC8\u6001\u6216\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u91CD\u65B0\u5524\u9192\u4F60\u3002\n- \u521B\u5EFA worker \u65F6\u5FC5\u987B\u63D0\u4F9B\u7B80\u77ED\u3001\u5177\u4F53\u3001\u53EF\u533A\u5206\u7684 --title\uFF0C\u540C\u65F6\u628A\u5B8C\u6574\u4EFB\u52A1\u653E\u8FDB --message\uFF1B\u4E0D\u8981\u628A\u6574\u6BB5\u4EFB\u52A1\u6216 Session ID \u5F53\u6807\u9898\u3002\n- sessions read \u8FD4\u56DE\u7684\u662F\u7ED9\u7BA1\u7406\u8005\u770B\u7684\u7B80\u6D01\u8FDB\u5C55\u3001\u5DE5\u5177\u6458\u8981\u548C\u6700\u7EC8\u7ED3\u679C\uFF0C\u4E0D\u662F\u539F\u59CB\u6D41\u5F0F token\uFF1B\u4E0D\u8981\u8981\u6C42\u8BFB\u53D6\u6216\u8F6C\u8FF0\u5B8C\u6574\u6D41\u5F0F\u65E5\u5FD7\u3002\n- \u53EA\u80FD\u7BA1\u7406\u7531\u5F53\u524D Manager \u521B\u5EFA\u3001\u4E14\u4E0E\u4F60\u5904\u4E8E\u540C\u4E00\u53F0\u673A\u5668\u548C\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u7684 worker\uFF1B\u4E0D\u8981\u8DE8 Manager\u3001\u8DE8\u673A\u5668\u6216\u8DE8\u9879\u76EE\u8C03\u5EA6\u3002\n- \u4E0D\u8981\u65E0\u9650\u5FAA\u73AF\uFF1B\u6CA1\u6709\u660E\u786E\u4E0B\u4E00\u6B65\u65F6\u8BE2\u95EE\u7528\u6237\u6216\u7ED3\u675F\u5F53\u524D turn \u7B49\u5F85\u7CFB\u7EDF\u4E8B\u4EF6\u3002\n- \u4E0D\u8981\u81EA\u5DF1\u8BBE\u7F6E\u5B9A\u65F6\u5524\u9192\uFF1B\u795E\u5FF5\u4F1A\u5728\u7528\u6237\u6D88\u606F\u3001worker \u7EC8\u6001\u6216 worker \u957F\u8FD0\u884C\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u5524\u9192\u4F60\u3002\n- \u5916\u90E8\u6D88\u606F\u901A\u9053\u4E8B\u4EF6\u4F1A\u50CF\u666E\u901A\u7528\u6237\u6D88\u606F\u4E00\u6837\u9001\u8FBE\uFF0C\u683C\u5F0F\u4E3A\u201C\u5916\u90E8\u5BF9\u8BDD: <\u5BF9\u8BDD\u540D\u79F0>\u201D\u540E\u8DDF\u201C\u5BF9\u65B9: <\u5185\u5BB9\u6216\u9644\u4EF6\u94FE\u63A5>\u201D\uFF0C\u53EF\u80FD\u662F\u5408\u5E76\u6D88\u606F\uFF0C\u4E5F\u53EF\u80FD\u5305\u542B\u56FE\u7247\u3001\u89C6\u9891\u6216\u6587\u4EF6 URL\u3002\n- \u5916\u90E8\u6D88\u606F\u662F\u5426\u9700\u8981\u56DE\u590D\u3001\u8FFD\u95EE\u3001\u5FFD\u7565\u3001\u8F6C\u4EA4\u5185\u90E8\u8D1F\u8D23\u4EBA\uFF0C\u6216\u521B\u5EFA/\u6307\u6D3E worker\uFF0C\u7531\u4F60\u6839\u636E\u9879\u76EE\u4E0A\u4E0B\u6587\u663E\u5F0F\u5224\u65AD\u3002\n- \u5BF9\u5916\u4F60\u662F\u5F53\u524D\u9879\u76EE\u7684\u9879\u76EE\u7ECF\u7406\uFF0C\u4E0D\u8981\u81EA\u79F0\u795E\u5FF5\u3001Manager Agent \u6216 worker\uFF0C\u4E5F\u4E0D\u8981\u89E3\u91CA\u5185\u90E8\u8C03\u5EA6\u673A\u5236\uFF1B\u53EA\u5728\u9700\u8981\u65F6\u7528\u201C\u6211\u8FD9\u8FB9/\u6211\u4EEC\u8FD9\u8FB9\u201D\u6C9F\u901A\u3002\n- \u4E0D\u8981\u628A\u6240\u6709\u7EC6\u8282\u585E\u8FDB\u5BF9\u8BDD\u4E0A\u4E0B\u6587\uFF1B\u9700\u8981\u957F\u671F\u4FDD\u5B58\u7684\u4FE1\u606F\u5199\u5230\u9879\u76EE .shennian/ \u4E0B\u3002\n\n\u9700\u8981\u7BA1\u7406 worker \u6216\u5916\u90E8\u901A\u9053\u65F6\uFF0C\u4F7F\u7528\u672C\u5730\u547D\u4EE4\uFF1A\n- shennian manager sessions list --json\n- shennian manager sessions start --agent <discover\u8FD4\u56DE\u7684type> --title <\u77ED\u6807\u9898> --message <\u5B8C\u6574\u4EFB\u52A1>\uFF08\u9ED8\u8BA4\u4F7F\u7528\u5F53\u524D Manager \u5DF2\u7ED1\u5B9A\u76EE\u5F55\uFF09\n- shennian manager sessions send --session-id <id> --message <text>\n- shennian manager sessions queue list --session-id <id> --json\n- shennian manager sessions queue edit --session-id <id> --message-id <queueMessageId> --message <text>\n- shennian manager sessions queue delete --session-id <id> --message-id <queueMessageId>\n- shennian manager sessions stop --session-id <id>\uFF08\u7EC8\u6B62\u6B63\u5728\u8FD0\u884C\u7684 worker\uFF1B\u4E5F\u53EF\u7528 terminate/kill \u522B\u540D\uFF09\n- shennian manager sessions read --session-id <id> --limit 200 --json\n- shennian manager sessions wait --session-id <id> --timeout <1-600>\uFF08\u53D7\u7BA1 Manager \u901A\u5E38\u7B49\u5F85\u7CFB\u7EDF\u4E8B\u4EF6\u5524\u9192\uFF0C\u4E0D\u4E3B\u52A8\u8F6E\u8BE2\uFF09\n- shennian manager memory path\n\n\u5982\u679C\u5BBF\u4E3B\u76F4\u63A5\u63D0\u4F9B\u4E86 shennian_manager_* \u5DE5\u5177\uFF0C\u4F18\u5148\u4F7F\u7528\u8FD9\u4E9B\u5DE5\u5177\u5B8C\u6210\u540C\u540D\u64CD\u4F5C\uFF0C\u4E0D\u8981\u518D\u901A\u8FC7 Shell \u8C03\u7528\u547D\u4EE4\u3002\u9650\u5236\u6743\u9650\u53EA\u7EA6\u675F Provider \u81EA\u8EAB\u7684\u6587\u4EF6\u3001Shell\u3001\u7F51\u7EDC\u548C\u666E\u901A\u5DE5\u5177\u80FD\u529B\uFF0C\u4E0D\u4F1A\u64A4\u9500\u795E\u5FF5\u6CE8\u5165\u7684 Manager Worker \u7BA1\u7406\u80FD\u529B\uFF1B\u4E0D\u5F97\u628A\u201C\u9650\u5236\u6743\u9650\u201D\u8BEF\u89E3\u4E3A\u4E0D\u80FD\u521B\u5EFA\u3001\u8BFB\u53D6\u3001\u53D1\u9001\u6216\u505C\u6B62 Worker\u3002\n\n\u53EF\u521B\u5EFA\u7684 worker Agent \u4EE5\u5F53\u524D daemon discovery \u8FD4\u56DE\u4E14 supportsWorker=true \u7684 Adapter \u4E3A\u51C6\uFF0C\u5305\u62EC\u901A\u8FC7 Agent Protocol \u63A5\u5165\u5E76\u901A\u8FC7\u5951\u7EA6\u7684 Runtime\uFF1B\u4E0D\u8981\u7EF4\u62A4\u6216\u731C\u6D4B Provider \u540D\u79F0\u767D\u540D\u5355\u3002sessions send \u59CB\u7EC8\u5148\u628A worker \u6D88\u606F\u4EA4\u7ED9\u672C\u673A durable queue\uFF1Aworker \u6B63\u5FD9\u65F6\u7B49\u5F85\uFF0C\u7A7A\u95F2\u65F6\u7ACB\u5373\u7531\u540C\u4E00\u8C03\u5EA6\u5668\u6267\u884C\u3002\u961F\u5217\u91CC\u7684\u672A\u6267\u884C\u6D88\u606F\u53EF\u4EE5 list/edit/delete\uFF1B\u5DF2\u7ECF\u5F00\u59CB\u6267\u884C\u7684\u6D88\u606F\u4E0D\u80FD\u7F16\u8F91\u6216\u5220\u9664\uFF0C\u53EA\u80FD stop \u540E\u91CD\u65B0\u53D1\u9001\u3002\n\n\u8FD9\u4E9B\u547D\u4EE4\u5DF2\u7ECF\u7531\u795E\u5FF5\u6CE8\u5165\u5F53\u524D Manager \u8EAB\u4EFD\u548C\u540C\u9879\u76EE\u6743\u9650\u8FB9\u754C\u3002\u4E0D\u8981\u5C1D\u8BD5\u4F2A\u9020 Manager session id\u3002";
2
2
  export declare function buildManagerPrompt(userText: string): string;
@@ -13,9 +13,9 @@ const s=`\u4F60\u662F\u9879\u76EE\u7ECF\u7406\uFF0C\u662F\u5F53\u524D\u9879\u76E
13
13
  - \u4E0D\u8981\u76F4\u63A5\u7F16\u8F91\u4E1A\u52A1\u4EE3\u7801\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u4EB2\u81EA\u6267\u884C\u3002
14
14
  - \u53EF\u4EE5\u8BFB\u53D6\u6587\u4EF6\u3001\u641C\u7D22\u9879\u76EE\u548C\u68C0\u67E5\u4E0A\u4E0B\u6587\uFF0C\u4EE5\u4FBF\u505A\u5224\u65AD\u3002
15
15
  - \u9700\u8981\u4FEE\u6539\u4EE3\u7801\u3001\u8FD0\u884C\u6D4B\u8BD5\u3001\u8C03\u7814\u65B9\u6848\u65F6\uFF0C\u4F18\u5148\u521B\u5EFA\u6216\u6307\u6D3E worker\u3002
16
- - \u6BCF\u6B21\u6536\u5230\u65B0\u4EFB\u52A1\u3001\u8865\u5145\u8981\u6C42\u3001\u7EA0\u504F\u6216\u5916\u90E8\u6D88\u606F\u65F6\uFF0C\u5148\u7528 sessions list \u67E5\u770B\u5F53\u524D Manager \u81EA\u5DF1\u521B\u5EFA\u7684 worker\uFF1B\u5982\u679C\u53EF\u80FD\u76F8\u5173\uFF0C\u518D\u7528 sessions read \u8BFB\u53D6\u5FC5\u8981\u6458\u8981\u540E\u5224\u65AD\u662F\u5426\u590D\u7528\u3002\u4E0D\u8981\u5C1D\u8BD5\u53D1\u73B0\u6216\u63A7\u5236\u540C\u9879\u76EE\u4E2D\u5C5E\u4E8E\u65E7 Manager\u3001\u5176\u4ED6 Manager \u6216\u666E\u901A\u4F1A\u8BDD\u7684 Session\u3002
17
- - \u5982\u679C\u5DF2\u6709 worker \u6B63\u5728\u5904\u7406\u540C\u4E00\u76EE\u6807\u3001\u540C\u4E00\u529F\u80FD\u533A\u3001\u540C\u4E00\u6587\u4EF6\u8303\u56F4\u6216\u540C\u4E00\u95EE\u9898\u94FE\u8DEF\uFF0C\u4F18\u5148\u7528 sessions send \u628A\u65B0\u8981\u6C42\u53D1\u7ED9\u8FD9\u4E2A worker\uFF1B\u5373\u4F7F worker \u6B63\u5FD9\u4E5F\u53EF\u4EE5\u53D1\u9001\uFF0C\u9ED8\u8BA4\u4F1A\u8FDB\u5165\u672C\u673A\u961F\u5217\uFF0C\u4E0D\u8981\u56E0\u4E3A\u5B83\u5FD9\u5C31\u65B0\u5EFA worker\u3002
18
- - \u53EA\u6709\u6CA1\u6709\u76F8\u5173 worker\u3001\u73B0\u6709 worker \u5DF2\u660E\u663E\u4E0D\u9002\u5408\u7EE7\u7EED\u63A8\u8FDB\u3001\u6216\u4EFB\u52A1\u9700\u8981\u5E76\u884C\u62C6\u5206\u7ED9\u4E0D\u540C\u4E13\u957F\u65F6\uFF0C\u624D\u521B\u5EFA\u65B0\u7684 worker\u3002
16
+ - \u660E\u786E\u7684\u65B0\u76EE\u6807\u9ED8\u8BA4\u521B\u5EFA\u65B0\u7684 worker\uFF0C\u4E0D\u8981\u4E3A\u4E86\u590D\u7528\u800C\u5148\u679A\u4E3E\u5386\u53F2\uFF0C\u4E5F\u4E0D\u8981\u628A\u4E0D\u540C\u76EE\u6807\u6DF7\u8FDB\u65E7 worker\u3002
17
+ - \u53EA\u6709\u7528\u6237\u660E\u786E\u7EED\u63A5\u3001\u7EA0\u504F\u3001\u8865\u5145\u540C\u4E00\u95EE\u9898\u94FE\u8DEF\uFF0C\u6216\u7CFB\u7EDF\u6B63\u5728\u6062\u590D\u5148\u524D\u5B89\u6392\u65F6\uFF0C\u624D\u7528 sessions list \u67E5\u770B\u5F53\u524D Manager \u81EA\u5DF1\u521B\u5EFA\u7684 worker\uFF0C\u5E76\u7528 sessions read \u6838\u5BF9\u540E\u590D\u7528\u3002\u4E0D\u8981\u5C1D\u8BD5\u53D1\u73B0\u6216\u63A7\u5236\u540C\u9879\u76EE\u4E2D\u5C5E\u4E8E\u65E7 Manager\u3001\u5176\u4ED6 Manager \u6216\u666E\u901A\u4F1A\u8BDD\u7684 Session\u3002
18
+ - \u5982\u679C\u5DF2\u6709 worker \u6B63\u5728\u5904\u7406\u540C\u4E00\u76EE\u6807\uFF0C\u4F18\u5148\u7528 sessions send \u628A\u8865\u5145\u8981\u6C42\u53D1\u7ED9\u8FD9\u4E2A worker\uFF1B\u5373\u4F7F worker \u6B63\u5FD9\u4E5F\u53EF\u4EE5\u53D1\u9001\uFF0C\u9ED8\u8BA4\u4F1A\u8FDB\u5165\u672C\u673A\u961F\u5217\uFF0C\u4E0D\u8981\u56E0\u4E3A\u5B83\u5FD9\u5C31\u91CD\u590D\u521B\u5EFA\u3002
19
19
  - \u521B\u5EFA\u6216\u6307\u6D3E worker \u540E\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u5F53\u573A\u7EE7\u7EED\u8C03\u5EA6\uFF0C\u5426\u5219\u56DE\u590D\u7528\u6237\u5DF2\u5B89\u6392\u5E76\u7ED3\u675F\u5F53\u524D turn\uFF1B\u4E0D\u8981\u4E3B\u52A8\u8F6E\u8BE2 worker \u72B6\u6001\uFF0C\u795E\u5FF5\u4F1A\u5728 worker \u7EC8\u6001\u6216\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u91CD\u65B0\u5524\u9192\u4F60\u3002
20
20
  - \u521B\u5EFA worker \u65F6\u5FC5\u987B\u63D0\u4F9B\u7B80\u77ED\u3001\u5177\u4F53\u3001\u53EF\u533A\u5206\u7684 --title\uFF0C\u540C\u65F6\u628A\u5B8C\u6574\u4EFB\u52A1\u653E\u8FDB --message\uFF1B\u4E0D\u8981\u628A\u6574\u6BB5\u4EFB\u52A1\u6216 Session ID \u5F53\u6807\u9898\u3002
21
21
  - sessions read \u8FD4\u56DE\u7684\u662F\u7ED9\u7BA1\u7406\u8005\u770B\u7684\u7B80\u6D01\u8FDB\u5C55\u3001\u5DE5\u5177\u6458\u8981\u548C\u6700\u7EC8\u7ED3\u679C\uFF0C\u4E0D\u662F\u539F\u59CB\u6D41\u5F0F token\uFF1B\u4E0D\u8981\u8981\u6C42\u8BFB\u53D6\u6216\u8F6C\u8FF0\u5B8C\u6574\u6D41\u5F0F\u65E5\u5FD7\u3002
@@ -29,18 +29,18 @@ const s=`\u4F60\u662F\u9879\u76EE\u7ECF\u7406\uFF0C\u662F\u5F53\u524D\u9879\u76E
29
29
 
30
30
  \u9700\u8981\u7BA1\u7406 worker \u6216\u5916\u90E8\u901A\u9053\u65F6\uFF0C\u4F7F\u7528\u672C\u5730\u547D\u4EE4\uFF1A
31
31
  - shennian manager sessions list --json
32
- - shennian manager sessions start --agent <codex|claude|workbuddy|cursor|opencode|pi-coding-agent|custom:name> --workdir <path> --title <\u77ED\u6807\u9898> --message <\u5B8C\u6574\u4EFB\u52A1>
32
+ - shennian manager sessions start --agent <discover\u8FD4\u56DE\u7684type> --title <\u77ED\u6807\u9898> --message <\u5B8C\u6574\u4EFB\u52A1>\uFF08\u9ED8\u8BA4\u4F7F\u7528\u5F53\u524D Manager \u5DF2\u7ED1\u5B9A\u76EE\u5F55\uFF09
33
33
  - shennian manager sessions send --session-id <id> --message <text>
34
34
  - shennian manager sessions queue list --session-id <id> --json
35
35
  - shennian manager sessions queue edit --session-id <id> --message-id <queueMessageId> --message <text>
36
36
  - shennian manager sessions queue delete --session-id <id> --message-id <queueMessageId>
37
37
  - shennian manager sessions stop --session-id <id>\uFF08\u7EC8\u6B62\u6B63\u5728\u8FD0\u884C\u7684 worker\uFF1B\u4E5F\u53EF\u7528 terminate/kill \u522B\u540D\uFF09
38
38
  - shennian manager sessions read --session-id <id> --limit 200 --json
39
- - shennian manager sessions wait --session-id <id> --timeout <1-600>\uFF08\u4EC5\u5916\u90E8\u539F\u751F\u5BBF\u4E3B\u9700\u8981\u6709\u754C\u7B49\u5F85\u65F6\u4F7F\u7528\u4E00\u6B21\uFF1B\u53D7\u7BA1 Manager \u4E0D\u4E3B\u52A8\u8C03\u7528\uFF09
39
+ - shennian manager sessions wait --session-id <id> --timeout <1-600>\uFF08\u53D7\u7BA1 Manager \u901A\u5E38\u7B49\u5F85\u7CFB\u7EDF\u4E8B\u4EF6\u5524\u9192\uFF0C\u4E0D\u4E3B\u52A8\u8F6E\u8BE2\uFF09
40
40
  - shennian manager memory path
41
41
 
42
42
  \u5982\u679C\u5BBF\u4E3B\u76F4\u63A5\u63D0\u4F9B\u4E86 shennian_manager_* \u5DE5\u5177\uFF0C\u4F18\u5148\u4F7F\u7528\u8FD9\u4E9B\u5DE5\u5177\u5B8C\u6210\u540C\u540D\u64CD\u4F5C\uFF0C\u4E0D\u8981\u518D\u901A\u8FC7 Shell \u8C03\u7528\u547D\u4EE4\u3002\u9650\u5236\u6743\u9650\u53EA\u7EA6\u675F Provider \u81EA\u8EAB\u7684\u6587\u4EF6\u3001Shell\u3001\u7F51\u7EDC\u548C\u666E\u901A\u5DE5\u5177\u80FD\u529B\uFF0C\u4E0D\u4F1A\u64A4\u9500\u795E\u5FF5\u6CE8\u5165\u7684 Manager Worker \u7BA1\u7406\u80FD\u529B\uFF1B\u4E0D\u5F97\u628A\u201C\u9650\u5236\u6743\u9650\u201D\u8BEF\u89E3\u4E3A\u4E0D\u80FD\u521B\u5EFA\u3001\u8BFB\u53D6\u3001\u53D1\u9001\u6216\u505C\u6B62 Worker\u3002
43
43
 
44
- \u53EF\u521B\u5EFA\u7684 worker Agent \u5305\u62EC Codex\u3001Claude Code\u3001WorkBuddy\u3001Cursor\u3001opencode\u3001Pi\uFF0C\u4EE5\u53CA\u672C\u673A custom agent\uFF08custom:<name>\uFF09\u3002sessions send \u59CB\u7EC8\u5148\u628A worker \u6D88\u606F\u4EA4\u7ED9\u672C\u673A durable queue\uFF1Aworker \u6B63\u5FD9\u65F6\u7B49\u5F85\uFF0C\u7A7A\u95F2\u65F6\u7ACB\u5373\u7531\u540C\u4E00\u8C03\u5EA6\u5668\u6267\u884C\u3002\u961F\u5217\u91CC\u7684\u672A\u6267\u884C\u6D88\u606F\u53EF\u4EE5 list/edit/delete\uFF1B\u5DF2\u7ECF\u5F00\u59CB\u6267\u884C\u7684\u6D88\u606F\u4E0D\u80FD\u7F16\u8F91\u6216\u5220\u9664\uFF0C\u53EA\u80FD stop \u540E\u91CD\u65B0\u53D1\u9001\u3002
44
+ \u53EF\u521B\u5EFA\u7684 worker Agent \u4EE5\u5F53\u524D daemon discovery \u8FD4\u56DE\u4E14 supportsWorker=true \u7684 Adapter \u4E3A\u51C6\uFF0C\u5305\u62EC\u901A\u8FC7 Agent Protocol \u63A5\u5165\u5E76\u901A\u8FC7\u5951\u7EA6\u7684 Runtime\uFF1B\u4E0D\u8981\u7EF4\u62A4\u6216\u731C\u6D4B Provider \u540D\u79F0\u767D\u540D\u5355\u3002sessions send \u59CB\u7EC8\u5148\u628A worker \u6D88\u606F\u4EA4\u7ED9\u672C\u673A durable queue\uFF1Aworker \u6B63\u5FD9\u65F6\u7B49\u5F85\uFF0C\u7A7A\u95F2\u65F6\u7ACB\u5373\u7531\u540C\u4E00\u8C03\u5EA6\u5668\u6267\u884C\u3002\u961F\u5217\u91CC\u7684\u672A\u6267\u884C\u6D88\u606F\u53EF\u4EE5 list/edit/delete\uFF1B\u5DF2\u7ECF\u5F00\u59CB\u6267\u884C\u7684\u6D88\u606F\u4E0D\u80FD\u7F16\u8F91\u6216\u5220\u9664\uFF0C\u53EA\u80FD stop \u540E\u91CD\u65B0\u53D1\u9001\u3002
45
45
 
46
46
  \u8FD9\u4E9B\u547D\u4EE4\u5DF2\u7ECF\u7531\u795E\u5FF5\u6CE8\u5165\u5F53\u524D Manager \u8EAB\u4EFD\u548C\u540C\u9879\u76EE\u6743\u9650\u8FB9\u754C\u3002\u4E0D\u8981\u5C1D\u8BD5\u4F2A\u9020 Manager session id\u3002`;function n(e){return e}export{s as MANAGER_SYSTEM_PROMPT,n as buildManagerPrompt};
@@ -45,7 +45,7 @@ export type ManagerRecord = {
45
45
  /** This Manager Session originated from a managed Room and every synthetic
46
46
  * continuation must reacquire that Room's current trusted context. */
47
47
  managedRoomBound?: boolean;
48
- controllerKind?: 'managed_session' | 'native_skill';
48
+ controllerKind?: 'managed_session' | 'ephemeral';
49
49
  status: ManagerStatus;
50
50
  /** Event ids currently being transferred. Queue durable ACK ends inbox
51
51
  * ownership; Provider start is intentionally not a second acknowledgement. */
@@ -55,6 +55,26 @@ export type ManagerRecord = {
55
55
  createdAt: string;
56
56
  updatedAt: string;
57
57
  };
58
+ export type EphemeralWorkerAction = 'read' | 'send' | 'wait' | 'stop';
59
+ export type EphemeralWorkerGrant = {
60
+ refDigest: string;
61
+ managerSessionId: string;
62
+ workerSessionId: string;
63
+ workDir: string;
64
+ machineId: string;
65
+ accountNamespaceId: string | null;
66
+ actions: EphemeralWorkerAction[];
67
+ createdAt: string;
68
+ expiresAt: string;
69
+ revokedAt?: string | null;
70
+ };
71
+ export type WorkerRefResolution = {
72
+ ok: true;
73
+ grant: EphemeralWorkerGrant;
74
+ } | {
75
+ ok: false;
76
+ reason: 'invalid' | 'expired' | 'revoked';
77
+ };
58
78
  export type ManagerInboxEvent = {
59
79
  id: string;
60
80
  managerSessionId: string;
@@ -83,6 +103,7 @@ type RegistryFile = {
83
103
  workers: Record<string, ManagedWorkerRecord>;
84
104
  replyTargets: Record<string, ReplyTargetRecord>;
85
105
  inbox: Record<string, ManagerInboxEvent[]>;
106
+ workerGrants: Record<string, EphemeralWorkerGrant>;
86
107
  };
87
108
  export declare class ManagerRegistry {
88
109
  load(): RegistryFile;
@@ -98,11 +119,36 @@ export declare class ManagerRegistry {
98
119
  defaultWorkerAgentType?: AgentType | null;
99
120
  defaultWorkerModelId?: string | null;
100
121
  managedRoomBound?: boolean;
101
- controllerKind?: 'managed_session' | 'native_skill';
122
+ controllerKind?: 'managed_session' | 'ephemeral';
102
123
  status?: ManagerStatus;
103
124
  deliveringInboxEventIds?: string[];
104
125
  }): ManagerRecord;
105
126
  getManager(sessionId: string): ManagerRecord | undefined;
127
+ issueWorkerRef(input: {
128
+ managerSessionId: string;
129
+ workerSessionId: string;
130
+ workDir: string;
131
+ machineId: string;
132
+ accountNamespaceId?: string | null;
133
+ actions?: EphemeralWorkerAction[];
134
+ ttlMs?: number;
135
+ }): {
136
+ workerRef: string;
137
+ grant: EphemeralWorkerGrant;
138
+ };
139
+ resolveWorkerRef(input: {
140
+ workerRef: string;
141
+ action: EphemeralWorkerAction;
142
+ machineId: string;
143
+ accountNamespaceId?: string | null;
144
+ }): EphemeralWorkerGrant | undefined;
145
+ resolveWorkerRefDetailed(input: {
146
+ workerRef: string;
147
+ action: EphemeralWorkerAction;
148
+ machineId: string;
149
+ accountNamespaceId?: string | null;
150
+ }): WorkerRefResolution;
151
+ revokeWorkerRefsForWorker(workerSessionId: string): void;
106
152
  enqueueInboxEvent(input: Omit<ManagerInboxEvent, 'id' | 'createdAt' | 'updatedAt'>): ManagerInboxEvent | null;
107
153
  listInboxEvents(managerSessionId: string): ManagerInboxEvent[];
108
154
  removeInboxEvents(managerSessionId: string, eventIds: readonly string[]): void;
@@ -1 +1 @@
1
- import c from"node:fs";import I from"node:path";import{randomUUID as m}from"node:crypto";import{isAgentHiddenPayload as h,sessionPreviewFromPayload as k}from"@shennian/wire";import{resolveShennianPath as v}from"../config/index.js";import{readMessages as p}from"../session/store.js";const u=v("manager-registry.json");function l(){return new Date().toISOString()}function w(){return{managers:{},workers:{},replyTargets:{},inbox:{}}}class W{load(){try{const e=JSON.parse(c.readFileSync(u,"utf-8"));return{managers:e.managers??{},workers:e.workers??{},replyTargets:e.replyTargets??{},inbox:e.inbox??{}}}catch{return w()}}save(e){c.mkdirSync(I.dirname(u),{recursive:!0}),c.writeFileSync(u,JSON.stringify(e,null,2))}upsertManager(e){const s=this.load(),r=s.managers[e.sessionId],t=l(),n={sessionId:e.sessionId,agentSessionId:e.agentSessionId??r?.agentSessionId??null,workDir:e.workDir,machineId:e.machineId??r?.machineId??null,modelId:e.modelId,agentType:e.agentType??r?.agentType??"manager",providerModelId:e.providerModelId??r?.providerModelId??null,defaultWorkerAgentType:e.defaultWorkerAgentType??r?.defaultWorkerAgentType??null,defaultWorkerModelId:e.defaultWorkerModelId??r?.defaultWorkerModelId??null,managedRoomBound:e.managedRoomBound??r?.managedRoomBound??!1,controllerKind:e.controllerKind??r?.controllerKind??"managed_session",status:e.status??r?.status??"idle",deliveringInboxEventIds:e.deliveringInboxEventIds??r?.deliveringInboxEventIds??[],managedWorkerSessionIds:r?.managedWorkerSessionIds??[],attachedExternalChannels:r?.attachedExternalChannels??[],createdAt:r?.createdAt??t,updatedAt:t};return s.managers[e.sessionId]=n,this.save(s),n}getManager(e){return this.load().managers[e]}enqueueInboxEvent(e){const s=this.load(),r=s.managers[e.managerSessionId],t=s.workers[e.workerSessionId];if(!r||!t||t.managedBy!==r.sessionId||t.workDir!==r.workDir)return null;const n=l(),a=s.inbox[e.managerSessionId]??[],o=a.findIndex(g=>g.workerSessionId===e.workerSessionId&&(e.kind==="worker.health"?g.kind==="worker.health":g.kind===e.kind&&g.runId===e.runId)),i=o>=0?{...a[o],...e,updatedAt:n}:{...e,id:`mie_${m()}`,createdAt:n,updatedAt:n};return o>=0?a[o]=i:a.push(i),s.inbox[e.managerSessionId]=a,this.save(s),i}listInboxEvents(e){return[...this.load().inbox[e]??[]].sort((s,r)=>s.createdAt.localeCompare(r.createdAt))}removeInboxEvents(e,s){if(!s.length)return;const r=this.load(),t=new Set(s);r.inbox[e]=(r.inbox[e]??[]).filter(n=>!t.has(n.id)),this.save(r)}addWorker(e){const s=this.load(),r=s.managers[e.managerSessionId];if(!r)throw new Error("Manager runtime is not registered");const t=e.sessionId??`sess_worker_${m()}`,n=l(),a={sessionId:t,agentType:e.agentType,workDir:e.workDir,managedBy:e.managerSessionId,status:"running",createdAt:n,updatedAt:n,lastActivityAt:n,title:e.title??null,summary:e.summary??null,agentSessionId:null,runId:null};return s.workers[t]=a,r.managedWorkerSessionIds.includes(t)||r.managedWorkerSessionIds.push(t),r.updatedAt=n,this.save(s),a}updateWorker(e,s){const r=this.load(),t=r.workers[e];if(!t)return;const n={...t,...s,updatedAt:l()};return r.workers[e]=n,this.save(r),n}clearWorkerRunOrigins(){const e=this.load();let s=!1;for(const r of Object.values(e.workers))r.runRoomOrigins&&(delete r.runRoomOrigins,s=!0);for(const r of Object.values(e.inbox))for(const t of r)t.roomOrigin&&(delete t.roomOrigin,s=!0);s&&this.save(e)}listWorkers(e,s={}){const r=this.load(),t=r.managers[e];return t?Object.values(r.workers).filter(n=>n.managedBy===t.sessionId).filter(n=>n.workDir===t.workDir).filter(n=>s.includeManagers||n.agentType!=="manager").map(n=>this.decorateManagedWorker(n,s.runningSessionIds)).sort((n,a)=>a.lastActivityAt.localeCompare(n.lastActivityAt)):[]}getWorkerForManager(e,s){const r=this.load(),t=r.managers[e],n=r.workers[s];if(t&&n)return n.managedBy!==t.sessionId||n.workDir!==t.workDir||n.agentType==="manager"?void 0:this.decorateManagedWorker(n)}retireManager(e){const s=this.load(),r=Object.values(s.workers).filter(t=>t.managedBy===e);delete s.managers[e],delete s.inbox[e];for(const[t,n]of Object.entries(s.replyTargets))n.managerSessionId===e&&delete s.replyTargets[t];return this.save(s),r}completeRetiredWorker(e,s){const r=this.load(),t=r.workers[s];!t||t.managedBy!==e||(delete r.workers[s],this.save(r))}decorateManagedWorker(e,s){const r=s?.has(e.sessionId)?"running":e.status,t=this.readTranscriptSummary(e.sessionId,e.summary??null),n=t.lastMessagePreview??e.lastMessagePreview??e.summary??null,a=e.userGoal??t.userGoal??null;return{...e,managedBy:e.managedBy??null,status:r,title:e.title??a??n??e.summary??e.sessionId,userGoal:a,lastMessagePreview:n,summary:e.summary??n,canResume:e.canResume??e.status!=="aborted",source:e.source??"manager_registry",role:"managed_worker"}}readTranscriptSummary(e,s){const r=p(e,{limit:200});if(!r.length)return{userGoal:null,lastMessagePreview:s};const t=[...r].sort((o,i)=>o.ts-i.ts),n=t.find(o=>o.role==="user"),a=[...t].reverse().find(o=>y(o));return{userGoal:n?f(y(n),160):null,lastMessagePreview:f(y(a),180)??s}}createReplyTarget(e){const s=this.load(),r={replyTarget:`rt_${m()}`,managerSessionId:e.managerSessionId,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId??null,createdAt:l()};return s.replyTargets[r.replyTarget]=r,this.save(s),r}getReplyTarget(e){return this.load().replyTargets[e]}getLatestReplyTargetForManager(e){return Object.values(this.load().replyTargets).filter(s=>s.managerSessionId===e).sort((s,r)=>r.createdAt.localeCompare(s.createdAt))[0]}}function y(d){return!d||h(d.payload)?null:k(d.payload,180)}function f(d,e){return d?d.length>e?`${d.slice(0,e)}...`:d:null}export{W as ManagerRegistry};
1
+ import g from"node:fs";import h from"node:path";import{createHash as w,randomBytes as v,randomUUID as u}from"node:crypto";import{isAgentHiddenPayload as A,sessionPreviewFromPayload as S}from"@shennian/wire";import{resolveShennianPath as p}from"../config/index.js";import{readMessages as T}from"../session/store.js";const m=p("manager-registry.json");function d(){return new Date().toISOString()}function b(){return{managers:{},workers:{},replyTargets:{},inbox:{},workerGrants:{}}}function k(a){return w("sha256").update(a).digest("hex")}function I(a,e){try{g.chmodSync(a,e)}catch(s){if(!(s instanceof Error&&"code"in s&&s.code==="ENOENT"))throw s}}class E{load(){try{const e=JSON.parse(g.readFileSync(m,"utf-8"));return{managers:e.managers??{},workers:e.workers??{},replyTargets:e.replyTargets??{},inbox:e.inbox??{},workerGrants:e.workerGrants??{}}}catch{return b()}}save(e){const s=h.dirname(m);for(let r=0;r<3;r+=1)try{g.mkdirSync(s,{recursive:!0,mode:448}),process.platform!=="win32"&&I(s,448),g.writeFileSync(m,JSON.stringify(e,null,2),{mode:384}),process.platform!=="win32"&&I(m,384);return}catch(o){if(!(o instanceof Error&&"code"in o&&o.code==="ENOENT")||r===2)throw o}}upsertManager(e){const s=this.load(),r=s.managers[e.sessionId],o=d(),t={sessionId:e.sessionId,agentSessionId:e.agentSessionId??r?.agentSessionId??null,workDir:e.workDir,machineId:e.machineId??r?.machineId??null,modelId:e.modelId,agentType:e.agentType??r?.agentType??"manager",providerModelId:e.providerModelId??r?.providerModelId??null,defaultWorkerAgentType:e.defaultWorkerAgentType??r?.defaultWorkerAgentType??null,defaultWorkerModelId:e.defaultWorkerModelId??r?.defaultWorkerModelId??null,managedRoomBound:e.managedRoomBound??r?.managedRoomBound??!1,controllerKind:e.controllerKind??r?.controllerKind??"managed_session",status:e.status??r?.status??"idle",deliveringInboxEventIds:e.deliveringInboxEventIds??r?.deliveringInboxEventIds??[],managedWorkerSessionIds:r?.managedWorkerSessionIds??[],attachedExternalChannels:r?.attachedExternalChannels??[],createdAt:r?.createdAt??o,updatedAt:o};return s.managers[e.sessionId]=t,this.save(s),t}getManager(e){return this.load().managers[e]}issueWorkerRef(e){const s=this.load(),r=s.managers[e.managerSessionId],o=s.workers[e.workerSessionId];if(!r||r.controllerKind!=="ephemeral")throw new Error("ephemeral_controller_required");if(!o||o.managedBy!==r.sessionId||o.workDir!==e.workDir)throw new Error("worker_scope_mismatch");const t=`wr_${v(32).toString("base64url")}`,n=k(t),i=d(),l={refDigest:n,managerSessionId:r.sessionId,workerSessionId:o.sessionId,workDir:e.workDir,machineId:e.machineId,accountNamespaceId:e.accountNamespaceId??null,actions:e.actions??["read","send","wait","stop"],createdAt:i,expiresAt:new Date(Date.now()+(e.ttlMs??720*60*6e4)).toISOString(),revokedAt:null};return s.workerGrants[n]=l,this.save(s),{workerRef:t,grant:l}}resolveWorkerRef(e){const s=this.resolveWorkerRefDetailed(e);return s.ok?s.grant:void 0}resolveWorkerRefDetailed(e){if(!/^wr_[A-Za-z0-9_-]{40,}$/.test(e.workerRef))return{ok:!1,reason:"invalid"};const s=this.load(),r=s.workerGrants[k(e.workerRef)];if(!r)return{ok:!1,reason:"invalid"};if(r.revokedAt)return{ok:!1,reason:"revoked"};if(Date.parse(r.expiresAt)<=Date.now())return{ok:!1,reason:"expired"};if(r.machineId!==e.machineId)return{ok:!1,reason:"invalid"};if(r.accountNamespaceId!==(e.accountNamespaceId??null))return{ok:!1,reason:"invalid"};if(!r.actions.includes(e.action))return{ok:!1,reason:"invalid"};const o=s.managers[r.managerSessionId],t=s.workers[r.workerSessionId];return!o||o.controllerKind!=="ephemeral"?{ok:!1,reason:"revoked"}:!t||t.managedBy!==o.sessionId||t.workDir!==r.workDir?{ok:!1,reason:"revoked"}:{ok:!0,grant:r}}revokeWorkerRefsForWorker(e){const s=this.load(),r=d();let o=!1;for(const t of Object.values(s.workerGrants))t.workerSessionId!==e||t.revokedAt||(t.revokedAt=r,o=!0);o&&this.save(s)}enqueueInboxEvent(e){const s=this.load(),r=s.managers[e.managerSessionId],o=s.workers[e.workerSessionId];if(!r||!o||o.managedBy!==r.sessionId||o.workDir!==r.workDir)return null;const t=d(),n=s.inbox[e.managerSessionId]??[],i=n.findIndex(c=>c.workerSessionId===e.workerSessionId&&(e.kind==="worker.health"?c.kind==="worker.health":c.kind===e.kind&&c.runId===e.runId)),l=i>=0?{...n[i],...e,updatedAt:t}:{...e,id:`mie_${u()}`,createdAt:t,updatedAt:t};return i>=0?n[i]=l:n.push(l),s.inbox[e.managerSessionId]=n,this.save(s),l}listInboxEvents(e){return[...this.load().inbox[e]??[]].sort((s,r)=>s.createdAt.localeCompare(r.createdAt))}removeInboxEvents(e,s){if(!s.length)return;const r=this.load(),o=new Set(s);r.inbox[e]=(r.inbox[e]??[]).filter(t=>!o.has(t.id)),this.save(r)}addWorker(e){const s=this.load(),r=s.managers[e.managerSessionId];if(!r)throw new Error("Manager runtime is not registered");const o=e.sessionId??`sess_worker_${u()}`,t=d(),n={sessionId:o,agentType:e.agentType,workDir:e.workDir,managedBy:e.managerSessionId,status:"running",createdAt:t,updatedAt:t,lastActivityAt:t,title:e.title??null,summary:e.summary??null,agentSessionId:null,runId:null};return s.workers[o]=n,r.managedWorkerSessionIds.includes(o)||r.managedWorkerSessionIds.push(o),r.updatedAt=t,this.save(s),n}updateWorker(e,s){const r=this.load(),o=r.workers[e];if(!o)return;const t={...o,...s,updatedAt:d()};return r.workers[e]=t,this.save(r),t}clearWorkerRunOrigins(){const e=this.load();let s=!1;for(const r of Object.values(e.workers))r.runRoomOrigins&&(delete r.runRoomOrigins,s=!0);for(const r of Object.values(e.inbox))for(const o of r)o.roomOrigin&&(delete o.roomOrigin,s=!0);s&&this.save(e)}listWorkers(e,s={}){const r=this.load(),o=r.managers[e];return o?Object.values(r.workers).filter(t=>t.managedBy===o.sessionId).filter(t=>t.workDir===o.workDir).filter(t=>s.includeManagers||t.agentType!=="manager").map(t=>this.decorateManagedWorker(t,s.runningSessionIds)).sort((t,n)=>n.lastActivityAt.localeCompare(t.lastActivityAt)):[]}getWorkerForManager(e,s){const r=this.load(),o=r.managers[e],t=r.workers[s];if(o&&t)return t.managedBy!==o.sessionId||t.workDir!==o.workDir||t.agentType==="manager"?void 0:this.decorateManagedWorker(t)}retireManager(e){const s=this.load(),r=Object.values(s.workers).filter(t=>t.managedBy===e),o=d();for(const t of Object.values(s.workerGrants))t.managerSessionId===e&&!t.revokedAt&&(t.revokedAt=o);delete s.managers[e],delete s.inbox[e];for(const[t,n]of Object.entries(s.replyTargets))n.managerSessionId===e&&delete s.replyTargets[t];return this.save(s),r}completeRetiredWorker(e,s){const r=this.load(),o=r.workers[s];if(!o||o.managedBy!==e)return;const t=d();for(const n of Object.values(r.workerGrants))n.workerSessionId===s&&!n.revokedAt&&(n.revokedAt=t);delete r.workers[s],this.save(r)}decorateManagedWorker(e,s){const r=s?.has(e.sessionId)?"running":e.status,o=this.readTranscriptSummary(e.sessionId,e.summary??null),t=o.lastMessagePreview??e.lastMessagePreview??e.summary??null,n=e.userGoal??o.userGoal??null;return{...e,managedBy:e.managedBy??null,status:r,title:e.title??n??t??e.summary??e.sessionId,userGoal:n,lastMessagePreview:t,summary:e.summary??t,canResume:e.canResume??e.status!=="aborted",source:e.source??"manager_registry",role:"managed_worker"}}readTranscriptSummary(e,s){const r=T(e,{limit:200});if(!r.length)return{userGoal:null,lastMessagePreview:s};const o=[...r].sort((i,l)=>i.ts-l.ts),t=o.find(i=>i.role==="user"),n=[...o].reverse().find(i=>f(i));return{userGoal:t?y(f(t),160):null,lastMessagePreview:y(f(n),180)??s}}createReplyTarget(e){const s=this.load(),r={replyTarget:`rt_${u()}`,managerSessionId:e.managerSessionId,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId??null,createdAt:d()};return s.replyTargets[r.replyTarget]=r,this.save(s),r}getReplyTarget(e){return this.load().replyTargets[e]}getLatestReplyTargetForManager(e){return Object.values(this.load().replyTargets).filter(s=>s.managerSessionId===e).sort((s,r)=>r.createdAt.localeCompare(s.createdAt))[0]}}function f(a){return!a||A(a.payload)?null:S(a.payload,180)}function y(a,e){return a?a.length>e?`${a.slice(0,e)}...`:a:null}export{E as ManagerRegistry};
@@ -50,12 +50,12 @@ export declare class ManagerRuntimeService {
50
50
  defaultWorkerAgentType?: AgentType | null;
51
51
  defaultWorkerModelId?: string | null;
52
52
  managedRoomBound?: boolean;
53
- controllerKind?: 'managed_session' | 'native_skill';
53
+ controllerKind?: 'managed_session' | 'ephemeral';
54
54
  status?: 'idle' | 'running' | 'interrupting';
55
55
  }): void;
56
56
  setManagedAgentPolicy(sessionId: string, policy: CompiledManagedAgentPolicy | null): void;
57
57
  getManagedAgentPolicyForWorker(managerSessionId: string, workDir: string): CompiledManagedAgentPolicy | null;
58
- isNativeSkillManager(managerSessionId: string, workDir: string): boolean;
58
+ isExternalWorkerController(managerSessionId: string, workDir: string): boolean;
59
59
  isManagedWorkerSession(sessionId: string): boolean;
60
60
  isManagedRoomBoundManager(sessionId: string): boolean;
61
61
  retireManagerAndWorkers(managerSessionId: string): Promise<string[]>;
@@ -1,13 +1,13 @@
1
- import N from"node:http";import{createHash as _,randomBytes as T,randomUUID as k}from"node:crypto";import m from"node:fs";import E from"node:os";import M from"node:path";import{ManagerRegistry as W}from"./registry.js";import{appendMessage as O}from"../session/store.js";import{requestSessionIndexPublish as D}from"../session/index-publisher.js";import{RetiredChannelRuntime as q}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{handleWorkerIpcRoute as A}from"./worker-ipc.js";import{deriveWorkerTitle as B,formatWorkerMarkdown as H}from"./worker-markdown.js";import{markTrustedManagerEvent as S}from"../session/manager-event-authority.js";const $=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024),y=12*6e4;let b=null;function ue(u){b=u}function ge(){return b}function p(u){return M.resolve(u||E.homedir())}function f(u,e,t){u.writeHead(e,{"content-type":"application/json; charset=utf-8"}),u.end(JSON.stringify(t))}function R(u){return u.replace(/\r\n/g,`
2
- `).trim()}function U(u){return u==="worker.final"?"completed":u==="worker.error"?"failed":u==="worker.aborted"?"stopped":"progress"}function j(u,e){return`manager-inbox:${_("sha256").update(u).update("\0").update(e.map(r=>r.id).join("\0")).digest("hex").slice(0,32)}`}async function G(u){const e=[];let t=0;for await(const r of u){const n=Buffer.isBuffer(r)?r:Buffer.from(r);if(t+=n.length,t>$)throw new Error("Request body too large");e.push(n)}return e.length===0?{}:JSON.parse(Buffer.concat(e).toString("utf8"))}function F(u,e,t,r){u.client.sendRes({type:"res",id:e,ok:t,...t?{payload:r}:{error:String(r.error||"unknown error")}})}function x(){return J()?M.join(E.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):P("runtime","manager-ipc.json")}function J(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class de{opts;managedPolicies=new Map;registry=new W;channelRuntime;server=null;ipcUrl=null;ipcToken=T(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 q}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.registry.clearWorkerRunOrigins(),this.server=N.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();m.mkdirSync(M.dirname(e),{recursive:!0}),m.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),m.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=x(),t=JSON.parse(m.readFileSync(e,"utf-8"));(t.url===this.ipcUrl||t.token===this.ipcToken)&&m.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:p(r),SHENNIAN_MANAGER_MODEL:n,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:p(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||p(t)!==r.workDir?null:n}isNativeSkillManager(e,t){const r=this.registry.getManager(e);return r?.controllerKind==="native_skill"&&p(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=[],i=async(s,d)=>{const a=[];try{r.runJournal?.markSessionAborted(s)}catch(l){a.push(l)}try{r.recoveryBarrier?.end(s)}catch(l){a.push(l)}try{r.chatQueue?.purgeSession(s)}catch(l){a.push(l)}const o=r.sessions.get(s);let c=!o;if(o){if(o.managedRoomContextToken)try{r.revokeManagedRoomContextToken?.(o.managedRoomContextToken),o.managedRoomContextToken=null}catch(l){a.push(l)}try{await o.adapter.stop(),c=!0}catch(l){a.push(l)}}if(o&&c&&a.length===0&&(r.sessions.delete(s),o.heartbeatTimer&&clearInterval(o.heartbeatTimer),o.adapter.removeAllListeners()),c&&a.length===0)try{d?.()}catch(l){a.push(l),n.push(l)}else n.push(...a)};await i(e);for(const s of t)await i(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 i=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:t,workDir:p(r),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:n,agentType:i?.agentType,providerModelId:i?.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 a=this.pendingWorkerOrigins.get(e);this.pendingWorkerOrigins.delete(e),a&&(n.runRoomOrigins={...r.runRoomOrigins??{},[t.runId]:a})}const i=`${e}:${t.runId}`;let s=t.message||r.summary||"(\u65E0\u53EF\u89C1\u5185\u5BB9)";if(t.state==="delta"&&t.text&&!t.thinking){const a=(this.workerTextAcc.get(i)??"")+t.text;this.workerTextAcc.set(i,a);const o=R(a);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 a=R(this.workerTextAcc.get(i)??"");if(a&&(n.summary=a.length>240?`${a.slice(0,240)}...`:a,s=t.message||a),this.workerTextAcc.delete(i),r.runRoomOrigins?.[t.runId]){const o={...r.runRoomOrigins};delete o[t.runId],n.runRoomOrigins=o}}else t.state==="start"&&(n.status="running");const d=this.registry.updateWorker(e,n);d&&(t.state==="final"||t.state==="error"||t.state==="aborted")&&this.enqueueWorkerEvent(d.managedBy,d,t.state,t.runId,s,r.runRoomOrigins?.[t.runId]??null)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){F(this.opts.getRuntime(),e.id,!1,{error:"feature_retired"})}async handleIpc(e,t){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){f(t,401,{ok:!1,error:"Unauthorized"});return}try{const r=new URL(e.url??"/","http://127.0.0.1"),n=await G(e);if(r.pathname.startsWith("/channel/")||r.pathname.startsWith("/external/")||r.pathname.startsWith("/wechat-rpa/")){f(t,410,{ok:!1,error:"feature_retired"});return}const i=await A({registry:this.registry,getRuntime:this.opts.getRuntime,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},r.pathname,n,String(e.headers["x-shennian-manager-session-id"]||""));if(i){f(t,i.status,i.body);return}f(t,404,{ok:!1,error:`Unknown manager IPC path: ${r.pathname}`})}catch(r){f(t,400,{ok:!1,error:r instanceof Error?r.message:String(r)})}}async executeScopedWorkerAction(e,t,r,n){const i={"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 A({registry:this.registry,getRuntime:this.opts.getRuntime,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},i[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,i,s,d,a){if((await this.opts.dispatchReq(S({type:"req",id:`manager-enqueue-${k()}`,method:"chat.enqueue",params:{...w(),sessionId:e,text:n,agentType:t,workDir:r,agentSessionId:i,modelId:s,managedWorkerParentSessionId:d,managerWorkerRoomOrigin:a}})))?.durableQueueHandoff===!1)throw new Error("manager_worker_queue_handoff_unconfirmed")}appendManagerVisibleMarkdown(e,t,r){this.registry.getManager(e)?.controllerKind!=="native_skill"&&(O(e,{id:r,sessionId:e,role:"agent",ts:Date.now(),payload:t},"committed"),D(this.opts.getRuntime().client))}enqueueWorkerEvent(e,t,r,n,i,s){const d=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:i||t.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"}),a=this.registry.getManager(e);d&&a?.controllerKind!=="native_skill"&&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 i=e-Date.parse(n.createdAt);if(i<10*6e4)continue;const s=e-Date.parse(n.lastActivityAt),d=r.sessions.get(n.sessionId)?.currentRunId,a=!!r.chatQueue?.getSnapshot(n.sessionId).pending.length;if(!d){if(!a&&s>=10*6e4){const v="\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:v});I&&this.enqueueWorkerEvent(I.managedBy,I,"error",n.runId??void 0,v)}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 l=Math.max(0,Math.floor(s/6e4)),g=R(n.runId?this.workerTextAcc.get(`${n.sessionId}:${n.runId}`)??"":""),h=`\u5DF2\u8FD0\u884C ${Math.floor(i/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
1
+ import N from"node:http";import{createHash as _,randomBytes as T,randomUUID as k}from"node:crypto";import p from"node:fs";import v from"node:os";import M from"node:path";import{ManagerRegistry as W}from"./registry.js";import{appendMessage as O}from"../session/store.js";import{requestSessionIndexPublish as D}from"../session/index-publisher.js";import{RetiredChannelRuntime as q}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 A}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(u){b=u}function le(){return b}function f(u){return M.resolve(u||v.homedir())}function m(u,e,t){u.writeHead(e,{"content-type":"application/json; charset=utf-8"}),u.end(JSON.stringify(t))}function R(u){return u.replace(/\r\n/g,`
2
+ `).trim()}function G(u){return u==="worker.final"?"completed":u==="worker.error"?"failed":u==="worker.aborted"?"stopped":"progress"}function F(u,e){return`manager-inbox:${_("sha256").update(u).update("\0").update(e.map(r=>r.id).join("\0")).digest("hex").slice(0,32)}`}async function J(u){const e=[];let t=0;for await(const r of u){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(u,e,t,r){u.client.sendRes({type:"res",id:e,ok:t,...t?{payload:r}:{error:String(r.error||"unknown error")}})}function x(){return V()?M.join(v.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 he{opts;managedPolicies=new Map;registry=new W;channelRuntime;server=null;ipcUrl=null;ipcToken=T(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 q}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.registry.clearWorkerRunOrigins(),this.server=N.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,g)=>{const i=[];try{r.runJournal?.markSessionAborted(s)}catch(l){i.push(l)}try{r.recoveryBarrier?.end(s)}catch(l){i.push(l)}try{r.chatQueue?.purgeSession(s)}catch(l){i.push(l)}const o=r.sessions.get(s);let c=!o;if(o){if(o.managedRoomContextToken)try{r.revokeManagedRoomContextToken?.(o.managedRoomContextToken),o.managedRoomContextToken=null}catch(l){i.push(l)}try{await o.adapter.stop(),c=!0}catch(l){i.push(l)}}if(o&&c&&i.length===0&&(r.sessions.delete(s),o.heartbeatTimer&&clearInterval(o.heartbeatTimer),o.adapter.removeAllListeners()),c&&i.length===0)try{g?.()}catch(l){i.push(l),n.push(l)}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 g=this.registry.updateWorker(e,n);g&&(t.state==="final"||t.state==="error"||t.state==="aborted")&&this.enqueueWorkerEvent(g.managedBy,g,t.state,t.runId,s,r.runRoomOrigins?.[t.runId]??null)}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,dispatchReq:this.opts.dispatchReq,dispatchChatEnqueue:this.dispatchChatEnqueue.bind(this),appendManagerVisibleMarkdown:this.appendManagerVisibleMarkdown.bind(this)},s=await H(a,r.pathname,n)??await A(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 A({registry:this.registry,getRuntime:this.opts.getRuntime,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,g,i){if((await this.opts.dispatchReq(S({type:"req",id:`manager-enqueue-${k()}`,method:"chat.enqueue",params:{...w(),sessionId:e,text:n,agentType:t,workDir:r,agentSessionId:a,modelId:s,managedWorkerParentSessionId:g,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"),D(this.opts.getRuntime().client))}enqueueWorkerEvent(e,t,r,n,a,s){const g=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)"}),i=this.registry.getManager(e);g&&i?.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),g=r.sessions.get(n.sessionId)?.currentRunId,i=!!r.chatQueue?.getSnapshot(n.sessionId).pending.length;if(!g){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 l=Math.max(0,Math.floor(s/6e4)),d=R(n.runId?this.workerTextAcc.get(`${n.sessionId}:${n.runId}`)??"":""),h=`\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 ${l} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:h}),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:h})&&c.controllerKind!=="native_skill"&&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(g=>g.priority==="high"),i=this.opts.getRuntime().sessions.get(e);if(t.status!=="idle"&&!n||i?.currentRunId&&!n)return;const d=(n??r[0]).roomOrigin??null,a=JSON.stringify(d),o=n?[n]:r.filter(g=>JSON.stringify(g.roomOrigin??null)===a).slice(0,20),c=o.map(g=>{const h=this.registry.getWorkerForManager(e,g.workerSessionId);return H({kind:U(g.kind),sessionId:g.workerSessionId,title:h?.title||B(h?.userGoal||h?.summary||g.summary),body:g.summary})}).join(`
7
+ - \u6700\u8FD1 ${l} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:h}),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:h})&&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 g=(n??r[0]).roomOrigin??null,i=JSON.stringify(g),o=n?[n]:r.filter(d=>JSON.stringify(d.roomOrigin??null)===i).slice(0,20),c=o.map(d=>{const h=this.registry.getWorkerForManager(e,d.workerSessionId);return U({kind:G(d.kind),sessionId:d.workerSessionId,title:h?.title||$(h?.userGoal||h?.summary||d.summary),body:d.summary})}).join(`
8
8
 
9
9
  ---
10
10
 
11
- `),l=j(e,o);try{n&&i?.currentRunId?(this.registry.upsertManager({...t,status:"interrupting",deliveringInboxEventIds:o.map(h=>h.id)}),await this.interruptAndResumeManager(t,c,l,d)):(this.registry.upsertManager({...t,status:"running",deliveringInboxEventIds:o.map(h=>h.id)}),await this.dispatchManagerContinuation(t,c,!0,l,d)),this.registry.removeInboxEvents(e,o.map(h=>h.id));const g=this.registry.getManager(e);g&&this.registry.upsertManager({...g,deliveringInboxEventIds:[]})}catch(g){const h=this.registry.getManager(e);h&&this.registry.upsertManager({...h,status:"idle",deliveringInboxEventIds:[]}),console.error(`[manager-inbox] delivery failed sessionId=${e}: ${g instanceof Error?g.message:String(g)}`)}}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 i=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,i),this.registry.listInboxEvents(r.sessionId).length>0&&queueMicrotask(()=>{this.drainManagerInbox(r.sessionId)})}}async dispatchManagerContinuation(e,t,r=!1,n,i){const s=e.agentType??"manager",d=s==="manager"?e.modelId:e.providerModelId??void 0,a={...w(),sessionId:e.sessionId,text:t,agentType:s,sessionMode:"manager",workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:d,managerDefaultWorkerAgentType:e.defaultWorkerAgentType??null,managerDefaultWorkerModelId:e.defaultWorkerModelId??null,...e.managedRoomBound?{managedAgentDirect:!0}:{},clientMessageId:n,managerEventId:n,...i?{managerEventRoomOrigin:i}:{}};if((await this.opts.dispatchReq(S({type:"req",id:`manager-${r?"enqueue":"send"}-${k()}`,method:"chat.enqueue",params:a})))?.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(`
11
+ `),l=F(e,o);try{n&&a?.currentRunId?(this.registry.upsertManager({...t,status:"interrupting",deliveringInboxEventIds:o.map(h=>h.id)}),await this.interruptAndResumeManager(t,c,l,g)):(this.registry.upsertManager({...t,status:"running",deliveringInboxEventIds:o.map(h=>h.id)}),await this.dispatchManagerContinuation(t,c,!0,l,g)),this.registry.removeInboxEvents(e,o.map(h=>h.id));const d=this.registry.getManager(e);d&&this.registry.upsertManager({...d,deliveringInboxEventIds:[]})}catch(d){const h=this.registry.getManager(e);h&&this.registry.upsertManager({...h,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",g=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:g,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-${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{de as ManagerRuntimeService,ge as getManagerRuntimeService,ue as setManagerRuntimeService};
13
+ `).trim()}}export{he as ManagerRuntimeService,le as getManagerRuntimeService,ge as setManagerRuntimeService};
@@ -13,7 +13,14 @@ type WorkerIpcHost = {
13
13
  dispatchChatEnqueue: (sessionId: string, agentType: AgentType, workDir: string, text: string, agentSessionId: string | null, modelId?: string, managedWorkerParentSessionId?: string, roomOrigin?: ManagedWorkerRoomOrigin) => Promise<void>;
14
14
  appendManagerVisibleMarkdown: (managerSessionId: string, markdown: string, id: string) => void;
15
15
  };
16
+ export declare class EphemeralWorkerError extends Error {
17
+ readonly code: string;
18
+ readonly nextAction: string;
19
+ readonly status: number;
20
+ constructor(code: string, message: string, nextAction: string, status?: number);
21
+ }
16
22
  export declare function handleWorkerIpcRoute(host: WorkerIpcHost, pathname: string, body: Record<string, unknown>, headerManagerSessionId: string, trustedOrigin?: {
17
23
  roomOrigin?: ManagedWorkerRoomOrigin;
18
24
  }): Promise<WorkerIpcResponse | null>;
25
+ export declare function handleEphemeralWorkerIpcRoute(host: WorkerIpcHost, pathname: string, body: Record<string, unknown>): Promise<WorkerIpcResponse | null>;
19
26
  export {};
@@ -1 +1 @@
1
- import w from"node:path";import{createHash as M,randomUUID as p}from"node:crypto";import{AVAILABLE_BUILTIN_AGENT_TYPES as q,extractPayloadText as W,isAgentHiddenPayload as E,isToolPayload as T}from"@shennian/wire";import{loadConfig as _}from"../config/index.js";import{getPersonalSyncIdentity as S}from"../personal-sync/epoch.js";import{readMessages as x,recordSession as D}from"../session/store.js";import{deriveWorkerTitle as k,formatWorkerMarkdown as f,normalizeWorkerTitle as b}from"./worker-markdown.js";function h(e){return w.resolve(e||process.cwd())}function v(e){return e==="manager"||e==="pi"?!1:e.startsWith("custom:")?!0:q.includes(e)}function A(e){return e==="worker.final"?"completed":e==="worker.error"?"failed":e==="worker.aborted"?"stopped":"progress"}function $(e,i){const s=[];for(const g of e)if(!E(g.payload))if(T(g.payload)){let c="tool",a="completed";try{const l=JSON.parse(g.payload);c=l.name||c,a=l.status||a}catch{}s.push({...g,payload:`[\u5DE5\u5177] ${c} \xB7 ${a}`})}else s.push({...g,payload:W(g.payload)});return s.slice(-Math.max(1,Math.min(i,500)))}async function O(e,i,s,g,c){if(i==="/skill/context"){const r=_(),o=String(s.host||""),n=String(s.hostSessionId||""),d=h(String(s.workDir||""));if(!r.machineId||!r.machineToken&&!r.accessToken)throw new Error("Shennian CLI must be paired and logged in");if(o!=="codex"&&o!=="claude")throw new Error("unsupported_native_agent_host");if(!n)throw new Error("native_agent_session_unavailable");if(!d||!w.isAbsolute(d))throw new Error("workDir is required");const m=`skill_manager_${M("sha256").update(r.machineId).update("\0").update(o).update("\0").update(n).update("\0").update(d).digest("hex").slice(0,32)}`;return e.registry.upsertManager({sessionId:m,agentSessionId:null,workDir:d,machineId:r.machineId,modelId:o,agentType:o,controllerKind:"native_skill",status:"idle"}),{status:200,body:{ok:!0,managerSessionId:m}}}if(!i.startsWith("/sessions/")&&i!=="/memory/path")return null;const a=String(s.managerSessionId||g||"");if(!a)throw new Error("managerSessionId is required");const l=e.registry.getManager(a);if(!l)throw new Error("Manager runtime is not registered");if(i==="/sessions/list"){const r=new Set([...e.getRuntime().sessions.entries()].filter(([,n])=>!!n.currentRunId).map(([n])=>n));return{status:200,body:{ok:!0,sessions:e.registry.listWorkers(a,{runningSessionIds:r})}}}if(i==="/memory/path")return{status:200,body:{ok:!0,path:w.join(l.workDir,".shennian")}};if(i==="/sessions/start"){const r=String(s.agentType||s.agent||l.defaultWorkerAgentType||"codex");if(!v(r))throw new Error(`Unsupported manager worker agent: ${r}`);const o=h(String(s.workDir||l.workDir));if(o!==l.workDir)throw new Error("Manager can only start workers in the same workDir");const n=String(s.message||"");if(!n)throw new Error("message is required");const d=s.title?b(String(s.title)):k(n),m=e.registry.addWorker({managerSessionId:a,agentType:r,workDir:o,title:d,summary:n.slice(0,120)}),I=String(s.modelId||(r===l.defaultWorkerAgentType?l.defaultWorkerModelId??"":""));D({sessionId:m.sessionId,agentType:r,workDir:o,agentSessionId:null,modelId:I||null,title:d,titleSource:"shennian_generated"}),await e.dispatchChatEnqueue(m.sessionId,r,o,n,null,I||void 0,a,c?.roomOrigin);const y=f({kind:"created",sessionId:m.sessionId,title:d,body:n});return e.appendManagerVisibleMarkdown(a,y,`worker-created:${m.sessionId}`),{status:200,body:{ok:!0,session:m,markdown:y}}}const u=String(s.sessionId||""),t=e.registry.getWorkerForManager(a,u);if(!t||t.role!=="managed_worker"||t.managedBy!==a)throw new Error("Worker not found in this manager scope");if(i==="/sessions/send"){const r=String(s.message||"");if(!r.trim())throw new Error("message is required");const o=s.enqueue===void 0?!0:!!s.enqueue,n=String(s.modelId||(t.agentType===l.defaultWorkerAgentType?l.defaultWorkerModelId??"":""));await e.dispatchChatEnqueue(t.sessionId,t.agentType,t.workDir,r,t.agentSessionId??null,n||void 0,a,c?.roomOrigin);const d=f({kind:"message",sessionId:u,title:t.title||k(t.userGoal||t.summary||r),body:r});return e.appendManagerVisibleMarkdown(a,d,`worker-message:${p()}`),{status:200,body:{ok:!0,markdown:d,queued:!0,directRequested:!o}}}if(i==="/sessions/queue")return{status:200,body:{ok:!0,queue:e.getRuntime().chatQueue?.getSnapshot(u)}};if(i==="/sessions/queue/edit"||i==="/sessions/queue/delete"){const r=i.endsWith("/edit");return await e.dispatchReq({type:"req",id:`manager-queue-${r?"edit":"delete"}-${p()}`,method:r?"chat.queue.edit":"chat.queue.delete",params:{...S(),sessionId:u,queueMessageId:String(s.queueMessageId||s.messageId||""),...r?{text:String(s.message||s.text||"")}:{}}}),{status:200,body:{ok:!0,queue:e.getRuntime().chatQueue?.getSnapshot(u)}}}if(i==="/sessions/stop"||i==="/sessions/terminate"){if(t.status==="final"||t.status==="error"||t.status==="aborted"){const r=t.status==="final"?"completed":t.status==="error"?"failed":"stopped";return{status:200,body:{ok:!0,alreadyTerminal:!0,status:t.status,markdown:f({kind:r,sessionId:u,title:t.title||k(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-${p()}`,method:"chat.abort",params:{...S(),sessionId:u}}),e.registry.updateWorker(u,{status:"aborted"}),{status:200,body:{ok:!0,markdown:f({kind:"stopped",sessionId:u,title:t.title||k(t.userGoal||t.summary||""),body:"\u5DF2\u505C\u6B62\u672C\u6B21\u6267\u884C\u3002"})}}}if(i==="/sessions/read"){const r=Number(s.limit||200),o=x(u,{limit:Math.max(r*20,r)});return{status:200,body:{ok:!0,messages:$(o,r),rawMessageCount:o.length}}}if(i==="/sessions/wait"){const r=Math.max(1e3,Math.min(Number(s.timeoutMs||6e5),6e5)),o=Date.now()+r;let n;for(;Date.now()<o&&(n=e.registry.listInboxEvents(a).find(m=>m.workerSessionId===u),!n);)await new Promise(m=>setTimeout(m,1e3));if(!n)return{status:200,body:{ok:!0,timedOut:!0,status:e.registry.getWorkerForManager(a,u)?.status}};const d=f({kind:A(n.kind),sessionId:u,title:t.title||k(t.userGoal||t.summary||n.summary),body:n.summary});return e.registry.removeInboxEvents(a,[n.id]),{status:200,body:{ok:!0,timedOut:!1,markdown:d,event:n}}}return null}export{O as handleWorkerIpcRoute};
1
+ import I 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,w=400){super(r),this.code=a,this.nextAction=i,this.status=w}}function x(e){return q.resolve(e||process.cwd())}function B(e){const a=x(e);let r;try{r=I.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=I.realpathSync.native(a);try{I.accessSync(i,I.constants.R_OK|I.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 M(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 w="tool",u="completed";try{const c=JSON.parse(i.payload);w=c.name||w,u=c.status||u}catch{}r.push({...i,payload:`[\u5DE5\u5177] ${w} \xB7 ${u}`})}else r.push({...i,payload:$(i.payload)});return r.slice(-Math.max(1,Math.min(a,500)))}async function b(e,a,r,i,w){if(!a.startsWith("/sessions/")&&a!=="/memory/path")return null;const u=String(r.managerSessionId||i||"");if(!u)throw new Error("managerSessionId is required");const c=e.registry.getManager(u);if(!c)throw new Error("Manager runtime is not registered");if(a==="/sessions/list"){const t=new Set([...e.getRuntime().sessions.entries()].filter(([,d])=>!!d.currentRunId).map(([d])=>d));return{status:200,body:{ok:!0,sessions:e.registry.listWorkers(u,{runningSessionIds:t})}}}if(a==="/memory/path")return{status:200,body:{ok:!0,path:q.join(c.workDir,".shennian")}};if(a==="/sessions/start"){const t=String(r.agentType||r.agent||c.defaultWorkerAgentType||"codex");if(!M(t))throw new Error(`Unsupported manager worker agent: ${t}`);const o=x(String(r.workDir||c.workDir));if(o!==c.workDir)throw new Error("Manager can only start workers in the same workDir");const d=String(r.message||"");if(!d)throw new Error("message is required");const m=r.title?L(String(r.title)):p(d),n=e.registry.addWorker({managerSessionId:u,agentType:t,workDir:o,title:m,summary:d.slice(0,120)}),k=String(r.modelId||(t===c.defaultWorkerAgentType?c.defaultWorkerModelId??"":""));K({sessionId:n.sessionId,agentType:t,workDir:o,agentSessionId:null,modelId:k||null,title:m,titleSource:"shennian_generated"}),await e.dispatchChatEnqueue(n.sessionId,t,o,d,null,k||void 0,u,w?.roomOrigin);const f=h({kind:"created",sessionId:n.sessionId,title:m,body:d});return e.appendManagerVisibleMarkdown(u,f,`worker-created:${n.sessionId}`),{status:200,body:{ok:!0,session:n,markdown:f}}}const l=String(r.sessionId||""),s=e.registry.getWorkerForManager(u,l);if(!s||s.role!=="managed_worker"||s.managedBy!==u)throw new Error("Worker not found in this manager scope");if(a==="/sessions/send"){const t=String(r.message||"");if(!t.trim())throw new Error("message is required");const o=r.enqueue===void 0?!0:!!r.enqueue,d=String(r.modelId||(s.agentType===c.defaultWorkerAgentType?c.defaultWorkerModelId??"":""));await e.dispatchChatEnqueue(s.sessionId,s.agentType,s.workDir,t,s.agentSessionId??null,d||void 0,u,w?.roomOrigin);const m=h({kind:"message",sessionId:l,title:s.title||p(s.userGoal||s.summary||t),body:t});return e.appendManagerVisibleMarkdown(u,m,`worker-message:${W()}`),{status:200,body:{ok:!0,markdown:m,queued:!0,directRequested:!o}}}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 t=a.endsWith("/edit");return await e.dispatchReq({type:"req",id:`manager-queue-${t?"edit":"delete"}-${W()}`,method:t?"chat.queue.edit":"chat.queue.delete",params:{...T(),sessionId:l,queueMessageId:String(r.queueMessageId||r.messageId||""),...t?{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(s.status==="final"||s.status==="error"||s.status==="aborted"){const t=s.status==="final"?"completed":s.status==="error"?"failed":"stopped";return{status:200,body:{ok:!0,alreadyTerminal:!0,status:s.status,markdown:h({kind:t,sessionId:l,title:s.title||p(s.userGoal||s.summary||""),body:s.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:s.title||p(s.userGoal||s.summary||""),body:"\u5DF2\u505C\u6B62\u672C\u6B21\u6267\u884C\u3002"})}}}if(a==="/sessions/read"){const t=Number(r.limit||200),o=G(l,{limit:Math.max(t*20,t)});return{status:200,body:{ok:!0,messages:F(o,t),rawMessageCount:o.length}}}if(a==="/sessions/wait"){const t=Math.max(1e3,Math.min(Number(r.timeoutMs||6e5),6e5)),o=Date.now()+t;let d;for(;Date.now()<o&&(d=e.registry.listInboxEvents(u).find(n=>n.workerSessionId===l),!d);)await new Promise(n=>setTimeout(n,1e3));if(!d){const n=e.registry.getWorkerForManager(u,l);if(n&&(n.status==="final"||n.status==="error"||n.status==="aborted")){const k=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:k,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(d.kind),sessionId:l,title:s.title||p(s.userGoal||s.summary||d.summary),body:d.summary});return e.registry.removeInboxEvents(u,[d.id]),{status:200,body:{ok:!0,timedOut:!1,markdown:m,event:d}}}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 t=String(r.message||"").trim();if(!t)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||!M(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 d=await j({serverUrl:i.serverUrl,authToken:i.machineToken??i.accessToken}),m=N(d).find(y=>y.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(y=>y.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 k=B(String(r.workDir||process.cwd())),f=`ephemeral_controller_${W()}`;e.registry.upsertManager({sessionId:f,agentSessionId:null,workDir:k,machineId:i.machineId,modelId:"manager",agentType:"manager",controllerKind:"ephemeral",status:"idle"});let v;try{v=await b(e,"/sessions/start",{managerSessionId:f,agentType:o,workDir:k,title:r.title,modelId:n||void 0,message:t},"")}catch(y){const A=e.registry.retireManager(f);for(const D of A)e.registry.completeRetiredWorker(f,D.sessionId);throw y}const S=v.body.session,_=typeof S?.sessionId=="string"?S.sessionId:"";if(!_)throw new Error("worker_start_invalid_response");const{workerRef:E,grant:R}=e.registry.issueWorkerRef({managerSessionId:f,workerSessionId:_,workDir:k,machineId:i.machineId,accountNamespaceId:i.accountNamespaceId??null});return{status:200,body:{ok:!0,workerId:_,workerRef:E,resolvedWorkDir:k,agentType:o,status:typeof S?.status=="string"?S.status:"running",expiresAt:R.expiresAt}}}const w=H[a];if(!w)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 u=String(r.workerRef||r.ref||""),c=e.registry.resolveWorkerRefDetailed({workerRef:u,action:w.action,machineId:i.machineId,accountNamespaceId:i.accountNamespaceId??null});if(!c.ok){const t=c.reason==="expired"?"worker_ref_expired":c.reason==="revoked"?"worker_ref_revoked":"worker_ref_invalid";throw new g(t,`The Worker reference is ${c.reason} or belongs to another local scope.`,c.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=c.grant;let s;try{s=await b(e,w.managerPath,{...r,managerSessionId:l.managerSessionId,sessionId:l.workerSessionId},"")}catch(t){const o=t instanceof Error?t.message:String(t);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(!s)throw new g("worker_command_unavailable","The requested Worker operation is unavailable.","Use worker start, read, send, wait, or stop.",404);return{status:s.status,body:{...s.body,workerId:l.workerSessionId}}}export{g as EphemeralWorkerError,oe as handleEphemeralWorkerIpcRoute,b as handleWorkerIpcRoute};
@@ -1 +1 @@
1
- import{registerPiValidationSession as I}from"./pi-validation-session.js";import y from"node:fs/promises";import b from"node:path";import{randomUUID as C}from"node:crypto";import{isAvailableAgentType as E}from"@shennian/wire";import{createAgent as N}from"../../agents/adapter.js";import{buildManagedAgentEnv as S}from"../../agents/config-status.js";import{ManagerModeAdapter as T}from"../../agents/manager.js";import{NativeSessionLifecycleRegistry as O}from"../native-session-lifecycle.js";import{validateManagedAgentWorkDir as M}from"../../security/managed-agent-policy.js";const x="\u8BF7\u56DE\u590D hello\uFF0C\u7528\u4E8E\u786E\u8BA4\u4F60\u5DF2\u51C6\u5907\u597D\u3002",R=6e4,W=new O;async function L(e,r={}){try{return await P(e,r)}catch(t){const a=t instanceof Error?t.message:"";return{ok:!1,errorCode:a==="agent_validation_cleanup_failed"?a:v(t,"start")}}}async function P(e,r){if(!E(e.runtimeAdapter)||e.runtimeAdapter==="manager"||e.runtimeAdapter==="pi"||e.runtimeAdapter==="openclaw")return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};if(e.sessionMode==="manager"&&!["codex","claude","pi-coding-agent"].includes(e.runtimeAdapter))return{ok:!1,errorCode:"agent_validation_manager_unavailable"};if(!b.isAbsolute(e.workDir.trim()))return{ok:!1,errorCode:"agent_validation_workdir_invalid"};let t;try{t=await(r.resolveWorkDir??U)(e.workDir)}catch(i){return{ok:!1,errorCode:V(i)}}if(!(r.validateWorkDir?await r.validateWorkDir(t):(await M(t,async()=>t)).ok))return{ok:!1,errorCode:"agent_validation_workdir_invalid"};const s=(r.createAdapter??N)(e.runtimeAdapter);if(!s)return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};const o=e.sessionMode==="manager"?new T(s,e.runtimeAdapter):s;let f=`agent-validation-${C()}`,l,u="",m="",_=null,c=null;const D=new Promise(i=>{c=i}),p=i=>{i.agentSessionId&&(u=i.agentSessionId,r.ignoreNativeSession?.(e.runtimeAdapter,u)),i.state==="delta"&&!i.thinking&&i.text&&(m+=i.text),i.state==="final"?(i.text&&(m+=i.text),c?.({kind:"final"})):(i.state==="error"||i.state==="aborted")&&c?.({kind:"error",error:i.message??i.state})},w=i=>c?.({kind:"error",error:i});o.on("agentEvent",p),o.on("error",w);const h=async()=>{e.runtimeAdapter==="pi-coding-agent"&&(l=await(r.registerPiSession??I)(),f=l.sessionId),o.configure?.({sessionId:f,env:S(e.runtimeAdapter)});try{await o.start(f,t,null,{persistNativeSession:!1})}catch(n){return{ok:!1,errorCode:v(n,"start")}}const i=new Promise(n=>{_=setTimeout(()=>n({kind:"timeout"}),r.timeoutMs??R)}),k=o.send(x,e.modelId??void 0).then(()=>D).catch(n=>({kind:"error",error:n})),d=await Promise.race([k,i]);return d.kind==="timeout"?{ok:!1,errorCode:"agent_validation_response_timeout"}:d.kind==="error"?{ok:!1,errorCode:v(d.error,"response")}:m.toLowerCase().includes("hello")?{ok:!0}:{ok:!1,errorCode:"agent_validation_response_mismatch"}};let A,g=!1;try{A=await h()}finally{_&&clearTimeout(_),o.removeListener("agentEvent",p),o.removeListener("error",w);try{await o.stop()}catch{l&&(g=!0)}finally{await l?.dispose().catch(()=>{g=!0})}u&&(r.cleanupNativeSession??((k,d,n)=>{W.execute(k,d,{workDir:n}).catch(()=>{})}))(e.runtimeAdapter,u,t)}return g?{ok:!1,errorCode:"agent_validation_cleanup_failed"}:A}async function Y(e,r){const t=j(r.params);if(!t){e.client.sendRes({type:"res",id:r.id,ok:!1,error:"agent_validation_invalid_configuration"});return}const a=await L(t,{ignoreNativeSession:(s,o)=>{e.nativeFusion?.ignoreNativeSource(s,o)}});e.client.sendRes(a.ok?{type:"res",id:r.id,ok:!0,payload:{ready:!0}}:{type:"res",id:r.id,ok:!1,error:a.errorCode})}async function U(e){const r=e.trim();if(!r)throw Object.assign(new Error("workdir_required"),{code:"EINVAL"});const t=await y.realpath(r);if(!(await y.stat(t)).isDirectory())throw Object.assign(new Error("workdir_not_directory"),{code:"ENOTDIR"});return t}function V(e){const r=F(e);return r==="ENOENT"?"agent_validation_workdir_not_found":r==="ENOTDIR"?"agent_validation_workdir_not_directory":r==="EACCES"||r==="EPERM"?"agent_validation_workdir_unavailable":"agent_validation_workdir_invalid"}function v(e,r){const t=e instanceof Error?e.message:String(e);return t==="agent_validation_server_upgrade_required"?t:/model.*(?:not found|unavailable|unsupported|invalid)|unknown model/i.test(t)?"agent_validation_model_unavailable":/quota|insufficient.*(?:credit|balance)|billing|rate.?limit|\b429\b/i.test(t)?"agent_validation_quota_exceeded":/not found|enoent|command .* missing|is .* installed/i.test(t)?"agent_validation_runtime_unavailable":/unauth|authentication|not logged|login required|api key|token.*missing|forbidden|401|403/i.test(t)?"agent_validation_runtime_auth_required":/ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|network|fetch failed|connection.*(?:failed|closed|refused)/i.test(t)?"agent_validation_network_failed":r==="start"?"agent_validation_start_failed":"agent_validation_response_failed"}function F(e){return typeof e=="object"&&e&&"code"in e?String(e.code):""}function j(e){const r=typeof e.runtimeAdapter=="string"?e.runtimeAdapter:"",t=typeof e.workDir=="string"?e.workDir:"",a=e.sessionMode==="manager"?"manager":"standard";return!E(r)||!t.trim()?null:{runtimeAdapter:r,workDir:t,sessionMode:a,modelId:typeof e.modelId=="string"?e.modelId:null}}export{Y as handleAgentWorkspaceValidation,L as validateAgentWorkspaceConfiguration};
1
+ import{registerPiValidationSession as I}from"./pi-validation-session.js";import y from"node:fs/promises";import b from"node:path";import{randomUUID as C}from"node:crypto";import{isAvailableAgentType as E}from"@shennian/wire";import{createAgent as N}from"../../agents/adapter.js";import{buildManagedAgentEnv as S}from"../../agents/config-status.js";import{ManagerModeAdapter as T}from"../../agents/manager.js";import{NativeSessionLifecycleRegistry as O}from"../native-session-lifecycle.js";import{validateManagedAgentWorkDir as M}from"../../security/managed-agent-policy.js";const x="\u8BF7\u56DE\u590D hello\uFF0C\u7528\u4E8E\u786E\u8BA4\u4F60\u5DF2\u51C6\u5907\u597D\u3002",R=6e4,W=new O;async function L(e,r={}){try{return await P(e,r)}catch(t){const a=t instanceof Error?t.message:"";return{ok:!1,errorCode:a==="agent_validation_cleanup_failed"?a:v(t,"start")}}}async function P(e,r){if(!E(e.runtimeAdapter)||e.runtimeAdapter==="manager"||e.runtimeAdapter==="pi"||e.runtimeAdapter==="openclaw")return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};if(!b.isAbsolute(e.workDir.trim()))return{ok:!1,errorCode:"agent_validation_workdir_invalid"};let t;try{t=await(r.resolveWorkDir??U)(e.workDir)}catch(i){return{ok:!1,errorCode:V(i)}}if(!(r.validateWorkDir?await r.validateWorkDir(t):(await M(t,async()=>t)).ok))return{ok:!1,errorCode:"agent_validation_workdir_invalid"};const s=(r.createAdapter??N)(e.runtimeAdapter);if(!s)return{ok:!1,errorCode:"agent_validation_runtime_unavailable"};const o=e.sessionMode==="manager"?new T(s,e.runtimeAdapter):s;let f=`agent-validation-${C()}`,l,u="",m="",_=null,c=null;const D=new Promise(i=>{c=i}),p=i=>{i.agentSessionId&&(u=i.agentSessionId,r.ignoreNativeSession?.(e.runtimeAdapter,u)),i.state==="delta"&&!i.thinking&&i.text&&(m+=i.text),i.state==="final"?(i.text&&(m+=i.text),c?.({kind:"final"})):(i.state==="error"||i.state==="aborted")&&c?.({kind:"error",error:i.message??i.state})},w=i=>c?.({kind:"error",error:i});o.on("agentEvent",p),o.on("error",w);const h=async()=>{e.runtimeAdapter==="pi-coding-agent"&&(l=await(r.registerPiSession??I)(),f=l.sessionId),o.configure?.({sessionId:f,env:S(e.runtimeAdapter)});try{await o.start(f,t,null,{persistNativeSession:!1})}catch(n){return{ok:!1,errorCode:v(n,"start")}}const i=new Promise(n=>{_=setTimeout(()=>n({kind:"timeout"}),r.timeoutMs??R)}),k=o.send(x,e.modelId??void 0).then(()=>D).catch(n=>({kind:"error",error:n})),d=await Promise.race([k,i]);return d.kind==="timeout"?{ok:!1,errorCode:"agent_validation_response_timeout"}:d.kind==="error"?{ok:!1,errorCode:v(d.error,"response")}:m.toLowerCase().includes("hello")?{ok:!0}:{ok:!1,errorCode:"agent_validation_response_mismatch"}};let A,g=!1;try{A=await h()}finally{_&&clearTimeout(_),o.removeListener("agentEvent",p),o.removeListener("error",w);try{await o.stop()}catch{l&&(g=!0)}finally{await l?.dispose().catch(()=>{g=!0})}u&&(r.cleanupNativeSession??((k,d,n)=>{W.execute(k,d,{workDir:n}).catch(()=>{})}))(e.runtimeAdapter,u,t)}return g?{ok:!1,errorCode:"agent_validation_cleanup_failed"}:A}async function Y(e,r){const t=j(r.params);if(!t){e.client.sendRes({type:"res",id:r.id,ok:!1,error:"agent_validation_invalid_configuration"});return}const a=await L(t,{ignoreNativeSession:(s,o)=>{e.nativeFusion?.ignoreNativeSource(s,o)}});e.client.sendRes(a.ok?{type:"res",id:r.id,ok:!0,payload:{ready:!0}}:{type:"res",id:r.id,ok:!1,error:a.errorCode})}async function U(e){const r=e.trim();if(!r)throw Object.assign(new Error("workdir_required"),{code:"EINVAL"});const t=await y.realpath(r);if(!(await y.stat(t)).isDirectory())throw Object.assign(new Error("workdir_not_directory"),{code:"ENOTDIR"});return t}function V(e){const r=F(e);return r==="ENOENT"?"agent_validation_workdir_not_found":r==="ENOTDIR"?"agent_validation_workdir_not_directory":r==="EACCES"||r==="EPERM"?"agent_validation_workdir_unavailable":"agent_validation_workdir_invalid"}function v(e,r){const t=e instanceof Error?e.message:String(e);return t==="agent_validation_server_upgrade_required"?t:/model.*(?:not found|unavailable|unsupported|invalid)|unknown model/i.test(t)?"agent_validation_model_unavailable":/quota|insufficient.*(?:credit|balance)|billing|rate.?limit|\b429\b/i.test(t)?"agent_validation_quota_exceeded":/not found|enoent|command .* missing|is .* installed/i.test(t)?"agent_validation_runtime_unavailable":/unauth|authentication|not logged|login required|api key|token.*missing|forbidden|401|403/i.test(t)?"agent_validation_runtime_auth_required":/ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|network|fetch failed|connection.*(?:failed|closed|refused)/i.test(t)?"agent_validation_network_failed":r==="start"?"agent_validation_start_failed":"agent_validation_response_failed"}function F(e){return typeof e=="object"&&e&&"code"in e?String(e.code):""}function j(e){const r=typeof e.runtimeAdapter=="string"?e.runtimeAdapter:"",t=typeof e.workDir=="string"?e.workDir:"",a=e.sessionMode==="manager"?"manager":"standard";return!E(r)||!t.trim()?null:{runtimeAdapter:r,workDir:t,sessionMode:a,modelId:typeof e.modelId=="string"?e.modelId:null}}export{Y as handleAgentWorkspaceValidation,L as validateAgentWorkspaceConfiguration};