beeos-claw 0.5.98 → 0.6.0

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/index.js CHANGED
@@ -1 +1 @@
1
- import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{MessageClient}from"@beeos-ai/message-sdk";import{hasRuntimeReplyApi}from"./runtime/reply-router.js";import{PromptRunStore}from"./runtime/prompt-run-store.js";import{SessionWriteGate}from"./acp-gateway/session-write-gate.js";import{OpenClawSessionWriteQuiescence}from"./runtime/openclaw-session-write-quiescence.js";import{SessionMutationCoordinator}from"./runtime/session-mutation-coordinator.js";import{scheduleProcessRuntimeRestart}from"./runtime/runtime-restart-scheduler.js";import{createImInboundHandler}from"./im-inbound-handler.js";import{MessageServiceRecoveryScanner}from"./im-recovery-scanner.js";import{createTokenProvider}from"@beeos-ai/message-sdk/auth";import{registerA2ATools,TaskHandleRegistry}from"./a2a/tools.js";import{BEEOS_UPLOAD_TOOL_NAME,BEEOS_UPLOAD_TOOL_SCHEMA,BEEOS_UPLOAD_TOOL_DESCRIPTION,executeUpload}from"./upload/tool.js";import{BEEOS_LIST_FILES_TOOL_NAME,BEEOS_LIST_FILES_TOOL_SCHEMA,BEEOS_LIST_FILES_TOOL_DESCRIPTION,executeListFiles}from"./files/list-tool.js";import{BEEOS_FETCH_FILE_TOOL_NAME,BEEOS_FETCH_FILE_TOOL_SCHEMA,BEEOS_FETCH_FILE_TOOL_DESCRIPTION,executeFetchFile}from"./files/fetch-tool.js";import{writeUploadToolResult,cleanupUploadToolResultCache}from"./upload/result-cache.js";import{handleFileStage}from"./upload/file-stage.js";import{handleStatusRequest}from"./status.js";import{ACPServer}from"./acp-server.js";import{createObsEventWriter}from"./observability.js";import{resolvePluginVersion}from"./plugin-version.js";import{createTerminalWebSocketService}from"./terminal/websocket.js";import{TerminalManager}from"./terminal/session-manager.js";import{loadAgentKeyPair,agentBoundFetch,agentFetch}from"./agent-auth.js";import{readDiscoveryCache,writeDiscoveryCache}from"./discovery-cache.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{setUploadDeps}from"./upload/upload-file.js";import{registerLocalFileExtractor}from"./upload/local-file-extractors.js";import{browserScreenshotExtractor}from"./browser/screenshot-extractor.js";import{BEEOS_FILE_RESOURCE_ROUTING_RULE}from"./files/beeos-file-uri.js";import{CanvasStateManager}from"./canvas/state.js";import{CanvasRelayClient}from"./canvas/relay-client.js";import{RENDER_UI_TOOL_NAME,RENDER_UI_TOOL_DESCRIPTION,RENDER_UI_TOOL_SCHEMA,CLOSE_UI_TOOL_NAME,CLOSE_UI_TOOL_DESCRIPTION,CLOSE_UI_TOOL_SCHEMA,createRenderUiHandler,createCloseUiHandler,CANVAS_SYSTEM_PROMPT}from"./canvas/tools.js";import{FormSchemaRegistry}from"./surface/form-schema-registry.js";import{buildCronSchemas}from"./surface/cron-schemas.js";import{RENDER_FORM_TOOL_NAME,RENDER_FORM_TOOL_SCHEMA,buildRenderFormDescription,createRenderFormHandler}from"./surface/render-form-tool.js";import{ResourceDescriptorRegistry}from"./surface/resource-descriptor-registry.js";import{CRON_RESOURCE}from"./surface/cron-resource.js";import{generateActionTools}from"./surface/action-tool-generator.js";import{buildThirdPartyChannelSet,isThirdPartyChannel}from"./channel-detection.js";import{registerArouterMediaProviders}from"./media/index.js";import{homedir}from"node:os";import{join}from"node:path";import{RuntimeProductionAuthority}from"./runtime/runtime-production-authority.js";import{FileRuntimeCommandJournal}from"./runtime/runtime-command-journal.js";import{FileRuntimePoisonDeliveryJournal}from"./runtime/runtime-poison-delivery-journal.js";import{InProcessMcpToolBridge}from"./acp-gateway/mcp-tool-bridge.js";import{LocalEncryptedCredentialReferenceVault}from"./acp-gateway/mcp-credential-reference-port.js";import{AgentGatewayGrantJwksReadinessAdapter,RuntimeServiceTransportReadinessAdapter,createRuntimeServiceProductionCandidate}from"./runtime/runtime-service-production-candidate.js";import{MessageServiceRuntimeAuthenticatedFetch}from"./runtime/message-service-runtime-authenticated-fetch.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null,sharedCanvasState=null,sharedAcpServer=null;const sharedSessionWriteGate=new SessionWriteGate,sharedPromptRunStore=new PromptRunStore;let sharedSessionMutationCoordinator,sharedRuntimeAuthority,sharedMcpToolBridge,sharedMcpCredentialVault;const sharedTerminalManager=new TerminalManager;let _initializing=!1,_pluginRegisteredAt=0;const ED25519_PKCS8_PREFIX=Buffer.from("302e020100300506032b657004220420","hex");function agentKeyPairToIdentity(e){return{publicKeyBase64:()=>Buffer.from(e.publicKey).toString("base64"),sign:async r=>{const{createPrivateKey:t,sign:o}=await import("node:crypto"),a=t({key:32===e.privateKey.length?Buffer.concat([ED25519_PKCS8_PREFIX,Buffer.from(e.privateKey)]):Buffer.from(e.privateKey),format:"der",type:"pkcs8"});return new Uint8Array(o(null,Buffer.from(r),a))}}}let unhandledRejectionObserverInstalled=!1;function installUnhandledRejectionObserver(e,r,t){unhandledRejectionObserverInstalled||(unhandledRejectionObserverInstalled=!0,process.on("unhandledRejection",t=>{const o=t instanceof Error?t.message:String(t),a=t instanceof Error?t.stack:void 0;e.logger.error?.(`[beeos-claw] unhandledRejection: ${o}${a?`\n${a}`:""}`),r({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:o,error:{code:"unhandled_rejection",message:o,...a?{stack:a}:{}}})}),process.on("uncaughtException",t=>{const o=t instanceof Error?t.message:String(t);e.logger.error?.("[beeos-claw] uncaughtException:",t),r({component:"plugin",domain:"fatal",name:"uncaught_exception",severity:"error",summary:o,error:{code:"uncaught_exception",message:o}})}))}function initializePlugin(e){let r,t;try{r=e.runtime?.config?.loadConfig?.()}catch{}try{t=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:r})}catch(r){return e.logger.error?.("[beeos-claw] Failed to resolve config:",r),null}const o=t.config,a=buildThirdPartyChannelSet(r);a.size>0&&e.logger.info?.(`[beeos-claw] Third-party channels detected: ${[...a].join(", ")}`);const n=o.log.enabled?createObsEventWriter({dir:o.log.dir,redaction:{gatewayToken:o.gateway.token},logger:e.logger}):()=>{};n({component:"plugin",domain:"lifecycle",name:"lifecycle.startup",severity:"info",payload:{pluginVersion:PLUGIN_VERSION,pid:process.pid,nodeVersion:process.version,cwd:process.cwd()}}),n({component:"plugin",domain:"config",name:"config.resolved",severity:"info",payload:{config:o,sources:t.sources,openclawConfigPath:t.openclawConfigPath}});for(const r of t.validation)n({component:"plugin",domain:"config",name:"config.validation",severity:r.severity,summary:r.message,error:{code:r.code,message:r.message,nextSteps:r.nextSteps}}),"error"===r.severity?e.logger.error?.(`[beeos-claw] ${r.message}`):e.logger.warn?.(`[beeos-claw] ${r.message}`);if(t.validation.some(e=>"error"===e.severity)&&e.logger.error?.("[beeos-claw] Plugin has configuration errors — some features may not work."),o.keyFile)try{agentKeys=loadAgentKeyPair(o.keyFile);const r=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${r})`)}catch(r){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${r}`),e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no key pair — signed requests disabled)`)}else e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no keyFile configured)`);return agentKeys&&o.agentGatewayUrl&&(setUploadDeps({gatewayUrl:o.agentGatewayUrl,keys:agentKeys,logger:e.logger}),registerLocalFileExtractor(browserScreenshotExtractor),e.logger.info?.("[beeos-claw] Upload deps + browser screenshot extractor registered")),{cfg:o,writeObs:n,thirdPartyChannels:a,openclawConfig:r}}function registerUploadTools(e,r,t){const{cfg:o,writeObs:a}=r;cleanupUploadToolResultCache().catch(r=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(r)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(r,t,n){e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} params=${JSON.stringify(t)}`);const s=await executeUpload(t,o,agentKeys,n);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} ok=${s.ok}`),a({component:"upload-tool",domain:"tool",name:s.ok?"upload.success":"upload.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,fileCount:s.files?.length??0,error:s.error}}),(async t=>{try{await writeUploadToolResult(r,BEEOS_UPLOAD_TOOL_NAME,t),await cleanupUploadToolResultCache()}catch(t){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${r}: ${String(t)}`)}return t})(s)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,t){await handleFileStage(e,t,r.cfg)}}),e.registerHttpRoute({path:"/beeos/status",auth:"plugin",handler(e,r){handleStatusRequest(e,r,{publicKey:agentKeys?.publicKey??null,pluginVersion:PLUGIN_VERSION})}}),e.registerTool({name:BEEOS_LIST_FILES_TOOL_NAME,description:BEEOS_LIST_FILES_TOOL_DESCRIPTION,parameters:BEEOS_LIST_FILES_TOOL_SCHEMA,async execute(r,t,n){e.logger.debug?.(`[beeos_list_files] toolCallId=${r} params=${JSON.stringify(t)}`);const s=await executeListFiles(t,o,agentKeys,n);return a({component:"file-list-tool",domain:"tool",name:s.ok?"list_files.success":"list_files.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,fileCount:s.files?.length??0,error:s.error}}),s}}),e.registerTool({name:BEEOS_FETCH_FILE_TOOL_NAME,description:BEEOS_FETCH_FILE_TOOL_DESCRIPTION,parameters:BEEOS_FETCH_FILE_TOOL_SCHEMA,async execute(r,t,n){e.logger.debug?.(`[beeos_fetch_file] toolCallId=${r} params=${JSON.stringify(t)}`);const s=await executeFetchFile(t,o,agentKeys,{signal:n,resolvePath:e.resolvePath});return a({component:"file-fetch-tool",domain:"tool",name:s.ok?"fetch_file.success":"fetch_file.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,path:s.path,size:s.size,error:s.error}}),s}})}function registerServices(e,r,t){const{cfg:o,writeObs:a,thirdPartyChannels:n}=r,s=agentKeys?(e,r)=>agentFetch(e,agentKeys,r):void 0,i=agentKeys?(e,r)=>agentBoundFetch(e,agentKeys,r):void 0,l=agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):void 0,c=join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos");sharedMcpToolBridge??=new InProcessMcpToolBridge,!sharedMcpCredentialVault&&agentKeys&&l&&(sharedMcpCredentialVault=new LocalEncryptedCredentialReferenceVault(join(c,"mcp-credential-vault.json"),l,agentKeys.privateKey)),sharedMcpToolBridge.registerTools(e),!sharedRuntimeAuthority&&agentKeys&&o.agentGatewayUrl&&s&&i&&(sharedRuntimeAuthority=new RuntimeProductionAuthority({stateDirectory:c,agentGatewayUrl:o.agentGatewayUrl,agentFetch:s,boundAgentFetch:i,keys:agentKeys,logger:e.logger})),sharedSessionMutationCoordinator??=new SessionMutationCoordinator(sharedSessionWriteGate,new OpenClawSessionWriteQuiescence(e.logger,{agentId:o.gateway.agentId||"main"}),e.logger,{onRecoveryRequired:r=>{scheduleProcessRuntimeRestart(r,e.logger)}});const g=sharedSessionMutationCoordinator;sharedAcpServer||(sharedAcpServer=new ACPServer(o,e.logger,g,a,s,l,n,sharedTerminalManager,sharedRuntimeAuthority,void 0,sharedMcpToolBridge,sharedMcpCredentialVault,i));const d=sharedAcpServer;t.current=d;const p=sharedPromptRunStore,{enabled:u,url:m,principalId:f,a2aEnabled:y}=o.messageBus;let w;o.agentGatewayUrl&&agentKeys&&(w=createTokenProvider({agentGatewayUrl:o.agentGatewayUrl,identity:agentKeyPairToIdentity(agentKeys)}));const b=w?new MessageServiceRuntimeAuthenticatedFetch({tokenProvider:w,...o.messageBus.serviceUrl?{configuredServiceUrl:o.messageBus.serviceUrl}:{}}):void 0;e.registerService({id:"beeos-acp",async start(){const r=Date.now(),t=_pluginRegisteredAt?r-_pluginRegisteredAt:-1;e.logger.info?.(`[beeos-claw] beeos-acp service start() begin, sincePluginRegister=${t}ms, bridge.url=${o.bridge.url||"(empty)"}`);const a=await createRuntimeServiceProductionCandidate({...o.agentGatewayUrl&&s?{authorizationAdapter:new AgentGatewayGrantJwksReadinessAdapter({agentGatewayUrl:o.agentGatewayUrl})}:{},...b&&o.agentGatewayUrl&&i&&process.env.MESSAGE_RUNTIME_DELIVERY_KEY?.trim()?{transportAdapter:new RuntimeServiceTransportReadinessAdapter({agentGatewayUrl:o.agentGatewayUrl,messageService:b,boundAgentFetch:i,scopedDeliveryKey:process.env.MESSAGE_RUNTIME_DELIVERY_KEY.trim()})}:{},commandJournal:sharedRuntimeAuthority?.journal??new FileRuntimeCommandJournal(join(c,"runtime-command-journal.json")),poisonJournal:new FileRuntimePoisonDeliveryJournal(join(c,"runtime-poison-deliveries.json")),onConsumerDeliveryError:r=>e.logger.error?.("[runtime-service] delivery worker failed before acknowledgement; delivery retained for retry",r)});if(sharedRuntimeAuthority?.setRuntimeServiceMethods("ready"===a.status?a.serviceMethods:[]),sharedRuntimeAuthority?.setRuntimeResourceBindingContractRevision("ready"===a.status?a.resourceBindingContractRevision:void 0),d.setRuntimeRpcConsumerFactory("ready"===a.status?a.factory:void 0),"unavailable"===a.status?e.logger.warn?.("[runtime-service] capability fenced: %s",a.blockers.map(e=>e.id).join(",")):a.capabilityBlockers.length&&e.logger.warn?.("[runtime-service] method capability fenced: %s",a.capabilityBlockers.map(e=>e.method).join(",")),!o.bridge.url&&o.agentGatewayUrl&&s){const r=Date.now(),t=await discoverBridgeFromGW(o.agentGatewayUrl,s);if(e.logger.info?.(`[beeos-claw] bridge discovery took ${Date.now()-r}ms result=${t?.url??"null"}`),t)o.bridge.url=t.url,writeDiscoveryCache({bridge:{url:t.url,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${t.url}`);else{const r=readDiscoveryCache();r?.bridge?.url?(o.bridge.url=r.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${r.bridge.url}`)):e.logger.debug?.("[beeos-claw] Bridge URL not available (no GW, no cache) — bridge disabled")}}await d.start(o.bridge.url||void 0),e.logger.info?.(`[beeos-claw] ACP Server started (total service start: ${Date.now()-r}ms)`)},async stop(){await d.stop(),e.logger.info?.("[beeos-claw] ACP Server stopped")}});const h=u&&(w||o.messageBus.serviceUrl),_=h?d.getMessageClient():null;if(h&&_){_.on("disconnect",r=>{e.logger.warn?.("[beeos-claw] message bus disconnected (code=%s, reason=%s) — SDK will auto-reconnect",r?.code??"?",r?.reason??"?")}),_.on("error",r=>{e.logger.warn?.(`[beeos-claw] message bus error: ${r instanceof Error?r.message:String(r)}`)});let r=f||process.env.BEEOS_PRINCIPAL_ID||"";const t={serviceUrl:o.messageBus.serviceUrl||"https://msg.invalid",centrifugoUrl:m,tokenProvider:w,logger:e.logger},n=new Map;let s,i=!1;const l=async()=>{if(r||!w)return r;try{const t=await w("");r=t.identityId||"",r&&e.logger.info?.("[beeos-claw] Message bus principal resolved from Agent Gateway token: %s",r)}catch(r){e.logger.warn?.("[beeos-claw] Failed to resolve message bus principal from Agent Gateway token: %s",r instanceof Error?r.message:String(r))}return r},c=()=>{if(r)return[{localAgentId:d.getLocalAgentId(),principalId:r}];const e=d.getPlatformAgentMappings().map(({localAgentId:e,platformAgent:r})=>({localAgentId:e,principalId:r.instanceId||""})).filter(e=>e.principalId);if(e.length>0)return e;const t=d.getDefaultPlatformAgent()?.instanceId||"";return t?[{localAgentId:d.getLocalAgentId(),principalId:t}]:[]},u=e=>({getLocalAgentId:()=>e.localAgentId,getDefaultPlatformAgent:()=>({instanceId:e.principalId}),ensureImInboundSession:(e,r,t)=>{d.ensureImInboundSession(e,r,t)},chatAbort:(e,r)=>d.chatAbort(e,r)}),y=async(r,s)=>{if(n.has(r.principalId))return;const i=s?_:new MessageClient(t);s&&d.setMessageClient(t,r.principalId);const l=createImInboundHandler({runtime:e.runtime,pluginConfig:e.pluginConfig,client:i,localServer:u(r),promptRunStore:p,sessionMutationCoordinator:g,logger:e.logger,writeObs:a,principalIdOverride:r.principalId,promptTimeoutMs:o.promptTimeoutMs,activeRunAuthority:d.getActiveRunAuthority()}),c=new MessageServiceRecoveryScanner({client:i,imInbound:l,logger:e.logger});i.on("connect",()=>{c.trigger("reconnect"),e.logger.info?.("[beeos-claw] MessageClient connected (localAgentId=%s, principalId=%s, via=%s)",r.localAgentId,r.principalId,w?"gateway":"direct")}),i.on("message",r=>{(async()=>{"chat_message"===r.type?await l.onChatMessage(r):"chat_cancel"===r.type&&await l.onChatCancel(r)})().catch(r=>{e.logger.warn?.(`[beeos-claw] message handler failed: ${r instanceof Error?r.message:String(r)}`)})}),n.set(r.principalId,{client:i,scanner:c,primary:s});try{await i.connect({identityId:r.principalId}),c.start(r.principalId)}catch(e){throw n.delete(r.principalId),c.stop(),s||await i.disconnect().catch(()=>{}),e}},b=async r=>{if(i)if(s)await s;else{s=(async()=>{const t=c();if(0===t.length)return void e.logger.warn?.("[beeos-claw] No principalId resolved, message bus disabled");const o=new Set(t.map(e=>e.principalId));let a=!1;for(const[t,s]of n)o.has(t)||(s.primary&&(a=!0),s.scanner.stop(),await s.client.disconnect(),n.delete(t),e.logger.info?.("[beeos-claw] MessageClient disconnected stale identity principalId=%s reason=%s",t,r));if(a&&n.size>0)for(const[t,o]of n)o.scanner.stop(),await o.client.disconnect(),n.delete(t),e.logger.info?.("[beeos-claw] MessageClient disconnected for primary rebalance principalId=%s reason=%s",t,r);let s=[...n.values()].some(e=>e.primary);for(const e of t){const r=!s;await y(e,r),r&&(s=!0)}})();try{await s}finally{s=void 0}}};e.registerService({id:"beeos-message-bus",async start(){if(i=!0,d.onAgentMappingUpdated(()=>{b("agent-mapping-updated").catch(r=>{e.logger.error?.("[beeos-claw] Message bus mapping sync failed:",r)})}),await l(),!r)try{await d.waitForAgentMapping()}catch(r){e.logger.warn?.("[beeos-claw] Failed to resolve principalId from agent sync:",r)}await b("startup")},async stop(){i=!1;const r=[...n.values()];n.clear();for(const e of r)e.scanner.stop(),await e.client.disconnect();e.logger.info?.("[beeos-claw] MessageClient disconnected")}})}else u?e.logger.warn?.("[beeos-claw] Message bus enabled but MESSAGE_SERVICE_URL not set — disabled"):e.logger.info?.("[beeos-claw] Message bus disabled (enabled=%s)",u);if(!1!==y&&o.agentGatewayUrl){const r=e=>d.getGatewaySessionKeyForToolCall(e),t=e=>d.getMentionsForToolCall(e),a=(e,r)=>{d.broadcastNotification(e,r)},n=new TaskHandleRegistry;registerA2ATools(e,{agentGatewayUrl:o.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},n,r,t,a,()=>d?.getDefaultPlatformAgent()?.instanceId),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(o.canvasEnabled){sharedCanvasState||(sharedCanvasState=new CanvasStateManager);const r=sharedCanvasState;if(o.agentGatewayUrl&&agentKeys){let t;r.setLogger(e.logger);{const e=o.agentGatewayUrl,r=agentKeys;t=async()=>{const t=await agentFetch(`${e}/api/v1/canvas/token`,r,{method:"POST"});if(!t.ok){const e=await t.text().catch(()=>"");throw new Error(`Canvas token returned ${t.status}: ${e}`)}return t.json()}}const a=a=>{const n=new CanvasRelayClient({canvasRelayUrl:o.canvasRelayUrl,agentKeys:agentKeys,agentId:a.id,displayName:a.displayName,avatarUrl:a.avatarUrl,tokenProvider:t,onReconnect:()=>d.onRelayReconnected(),logger:e.logger});n.connect(),r.setRelayClient(n),r.setAgentId(a.id),d.wireRelayActionHandler(),d.flushPendingCanvasLinks(),e.logger.info?.(`[beeos-claw] Canvas Relay multiplexed client connected (agentId=${a.id}, name=${a.name??"none"})`)};e.registerService({id:"beeos-canvas-relay",async start(){try{await d.waitForAgentMapping();const r=d.getDefaultPlatformAgent();if(r?.id)return void a(r);e.logger.info?.("[beeos-claw] Canvas Relay deferred: waiting for late agent sync..."),d.onceAgentMappingReady(()=>{const r=d.getDefaultPlatformAgent();if(r?.id)try{a(r)}catch(r){e.logger.warn?.(`[beeos-claw] Deferred Canvas Relay start failed: ${r}`)}else e.logger.warn?.("[beeos-claw] Agent mapping ready but no platform agent; Canvas Relay stays disabled")})}catch(r){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${r}`)}},stop(){r.getRelayClient()?.disconnect()}}),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const t={stateManager:r,logger:e.logger,writeObs:e=>a(e),isCanvasActive:e=>!!e&&d.isCanvasActiveForGatewayKey(e),getActiveSessionId:e=>d.getSessionIdForToolCall(e),getPlatformAgentId:e=>d.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>d.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>d.waitForAgentMapping()},s=new FormSchemaRegistry,i=buildCronSchemas(n,{capMs:o.cronMaxDurationCapMs});s.register(i.create),s.register(i.update);const l=new ResourceDescriptorRegistry;l.register(CRON_RESOURCE);const c=e=>async(r,t,o)=>(d.preMapToolCallSession(r),e(r,t));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:c(createRenderUiHandler(t))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:c(createCloseUiHandler(t))}),d.setCanvasState(r,{schemaRegistry:s,descriptorRegistry:l});const g=d.getFormBuilder();g&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(s),parameters:RENDER_FORM_TOOL_SCHEMA,execute:c(createRenderFormHandler({registry:s,builder:g,logger:e.logger}))});const p=d.getResourceRenderer();if(p){generateActionTools(l,e,async(e,r)=>d.gatewayRequest(e,r),p,e.logger)}const u=`\n\nTool selection guidance:\n- browser tool: Use when the user wants to OPEN, VISIT, NAVIGATE to a webpage, take SCREENSHOTS, interact with elements (click, type), or visually inspect pages.\n- beeos_search: Use ONLY for text-based information retrieval (fact-finding, research queries). Never for page interaction or screenshots.\n- beeos_fetch: Use ONLY to extract article text from a known URL. Never for visual content or screenshots.\n- ${BEEOS_FILE_RESOURCE_ROUTING_RULE}\nWhen the user says "open", "visit", "go to", "screenshot", or mentions seeing/viewing a webpage, ALWAYS prefer the browser tool.`,m=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),f=256,y=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,r)=>{if(r?.sessionKey){if(y.size>=f&&!y.has(r.sessionKey)){const e=y.keys().next().value;void 0!==e&&y.delete(e)}y.set(r.sessionKey,r.channelId)}if(isThirdPartyChannel(n,{sessionKey:r?.sessionKey,channelId:r?.channelId}))return{appendSystemContext:'This conversation is in a third-party channel (Lark/Telegram/Discord/Slack); Canvas surfaces are not visible here. Do NOT use render_ui, close_ui, or render_form. Use built-in tools directly (e.g. the `cron` tool with `action:"add"` for scheduled tasks) and respond with plain text.'};if(!r?.sessionKey)return{appendSystemContext:u};return d.isCanvasActiveForGatewayKey(r.sessionKey)?{appendSystemContext:CANVAS_SYSTEM_PROMPT+u}:{appendSystemContext:"Canvas is closed by user. Do NOT use render_ui, close_ui, or render_form. Respond with plain text only."+u}}),e.logger.debug?.("[beeos-claw] Canvas system prompt hook registered: before_prompt_build")}catch{e.logger.debug?.("[beeos-claw] before_prompt_build hook not supported")}try{e.on("before_tool_call",(e,r)=>{if(r?.sessionKey&&d.setPendingToolCallSessionKey(r.sessionKey),!m.has(e.toolName))return;const t=r?.sessionKey?y.get(r.sessionKey):void 0;return isThirdPartyChannel(n,{sessionKey:r?.sessionKey,channelId:t})?{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."}:r?.sessionKey?d.isCanvasActiveForGatewayKey(r.sessionKey)?void 0:{block:!0,blockReason:"Canvas is closed by user. Respond with plain text instead."}:{block:!0,blockReason:"Canvas session unknown. Respond with plain text instead."}}),e.logger.debug?.("[beeos-claw] Canvas tool guard registered: before_tool_call")}catch{e.logger.debug?.("[beeos-claw] before_tool_call hook not supported")}}e.logger.info?.("[beeos-claw] Canvas tools registered (render_ui, close_ui, render_form + auto-action tools)")}else e.logger.info?.("[beeos-claw] Canvas disabled by config (canvasEnabled=false)");if(o.bridge.shell.enabled&&!0===o.legacyTerminalWsEnabled){const r=createTerminalWebSocketService({config:{port:o.terminalWsPort,token:agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):"",allowedOrigins:o.terminalWsAllowedOrigins},terminalMgr:sharedTerminalManager,logger:e.logger,writeObs:a});e.registerService({id:"beeos-terminal-ws",start(){r.start(),e.logger.info?.(`[beeos-claw] Terminal WS started on port ${o.terminalWsPort}`)},stop(){r.stop(),e.logger.info?.("[beeos-claw] Terminal WS stopped")}})}else o.bridge.shell.enabled?e.logger.info?.("[beeos-claw] Legacy static-token Terminal WS disabled (default)"):e.logger.info?.("[beeos-claw] Terminal/shell disabled by config (bridge.shell.enabled=false)");if(r.openclawConfig){const t=mirrorOpenClawConfig(r.openclawConfig);t.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${t.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${t.reason}`)}}const CAPABILITY_REGISTRATION_MODES=new Set(["full","discovery","tool-discovery"]),plugin={id:"beeos-claw",name:"beeos-claw",description:"BeeOS platform bridge plugin for OpenClaw. Provides file upload/download, ACP bridge, and observability.",version:PLUGIN_VERSION,register(e){if(!_initializing)if(!e.registrationMode||CAPABILITY_REGISTRATION_MODES.has(e.registrationMode)){_initializing=!0;try{if(!hasRuntimeReplyApi(e.runtime))throw e.logger.error?.("[beeos-claw] FATAL: OpenClaw runtime.channel.reply.dispatchReplyFromConfig not exposed. Upgrade OpenClaw or revert beeos-claw."),new Error("openclaw_runtime_reply_api_unavailable");const r=initializePlugin(e);if(!r)return;const t={current:null};installUnhandledRejectionObserver(e,r.writeObs,t),registerUploadTools(e,r,t),registerArouterMediaProviders(e),registerServices(e,r,t),_pluginRegisteredAt=Date.now(),e.logger.info?.("[beeos-claw] Plugin registered successfully")}finally{_initializing=!1}}else e.logger.debug?.(`[beeos-claw] Skipping registration (mode=${e.registrationMode})`)}};export default plugin;
1
+ import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{MessageClient}from"@beeos-ai/message-sdk";import{hasRuntimeReplyApi}from"./runtime/reply-router.js";import{PromptRunStore}from"./runtime/prompt-run-store.js";import{SessionWriteGate}from"./acp-gateway/session-write-gate.js";import{OpenClawSessionWriteQuiescence}from"./runtime/openclaw-session-write-quiescence.js";import{SessionMutationCoordinator}from"./runtime/session-mutation-coordinator.js";import{scheduleProcessRuntimeRestart}from"./runtime/runtime-restart-scheduler.js";import{createImInboundHandler}from"./im-inbound-handler.js";import{MessageServiceRecoveryScanner}from"./im-recovery-scanner.js";import{createTokenProvider}from"@beeos-ai/message-sdk/auth";import{registerA2ATools,TaskHandleRegistry}from"./a2a/tools.js";import{BEEOS_UPLOAD_TOOL_NAME,BEEOS_UPLOAD_TOOL_SCHEMA,BEEOS_UPLOAD_TOOL_DESCRIPTION,executeUpload}from"./upload/tool.js";import{BEEOS_LIST_FILES_TOOL_NAME,BEEOS_LIST_FILES_TOOL_SCHEMA,BEEOS_LIST_FILES_TOOL_DESCRIPTION,executeListFiles}from"./files/list-tool.js";import{BEEOS_FETCH_FILE_TOOL_NAME,BEEOS_FETCH_FILE_TOOL_SCHEMA,BEEOS_FETCH_FILE_TOOL_DESCRIPTION,executeFetchFile}from"./files/fetch-tool.js";import{writeUploadToolResult,cleanupUploadToolResultCache}from"./upload/result-cache.js";import{handleFileStage}from"./upload/file-stage.js";import{handleStatusRequest}from"./status.js";import{ACPServer}from"./acp-server.js";import{createObsEventWriter}from"./observability.js";import{resolvePluginVersion}from"./plugin-version.js";import{createTerminalWebSocketService}from"./terminal/websocket.js";import{TerminalManager}from"./terminal/session-manager.js";import{loadAgentKeyPair,agentBoundFetch,agentFetch}from"./agent-auth.js";import{readDiscoveryCache,writeDiscoveryCache}from"./discovery-cache.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{setUploadDeps}from"./upload/upload-file.js";import{registerLocalFileExtractor}from"./upload/local-file-extractors.js";import{browserScreenshotExtractor}from"./browser/screenshot-extractor.js";import{BEEOS_FILE_RESOURCE_ROUTING_RULE}from"./files/beeos-file-uri.js";import{CanvasStateManager}from"./canvas/state.js";import{CanvasRelayClient}from"./canvas/relay-client.js";import{RENDER_UI_TOOL_NAME,RENDER_UI_TOOL_DESCRIPTION,RENDER_UI_TOOL_SCHEMA,CLOSE_UI_TOOL_NAME,CLOSE_UI_TOOL_DESCRIPTION,CLOSE_UI_TOOL_SCHEMA,createRenderUiHandler,createCloseUiHandler,CANVAS_SYSTEM_PROMPT}from"./canvas/tools.js";import{FormSchemaRegistry}from"./surface/form-schema-registry.js";import{buildCronSchemas}from"./surface/cron-schemas.js";import{RENDER_FORM_TOOL_NAME,RENDER_FORM_TOOL_SCHEMA,buildRenderFormDescription,createRenderFormHandler}from"./surface/render-form-tool.js";import{ResourceDescriptorRegistry}from"./surface/resource-descriptor-registry.js";import{CRON_RESOURCE}from"./surface/cron-resource.js";import{generateActionTools}from"./surface/action-tool-generator.js";import{buildThirdPartyChannelSet,isThirdPartyChannel}from"./channel-detection.js";import{registerArouterMediaProviders}from"./media/index.js";import{homedir}from"node:os";import{join}from"node:path";import{RuntimeProductionAuthority}from"./runtime/runtime-production-authority.js";import{FileRuntimeCommandJournal}from"./runtime/runtime-command-journal.js";import{FileRuntimePoisonDeliveryJournal}from"./runtime/runtime-poison-delivery-journal.js";import{InProcessMcpToolBridge}from"./acp-gateway/mcp-tool-bridge.js";import{LocalEncryptedCredentialReferenceVault}from"./acp-gateway/mcp-credential-reference-port.js";import{AgentGatewayGrantJwksReadinessAdapter,RuntimeServiceTransportReadinessAdapter,createRuntimeServiceProductionCandidate}from"./runtime/runtime-service-production-candidate.js";import{MessageServiceRuntimeAuthenticatedFetch}from"./runtime/message-service-runtime-authenticated-fetch.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null,sharedCanvasState=null,sharedAcpServer=null;const sharedSessionWriteGate=new SessionWriteGate,sharedPromptRunStore=new PromptRunStore;let sharedSessionMutationCoordinator,sharedRuntimeAuthority,sharedMcpToolBridge,sharedMcpCredentialVault;const sharedTerminalManager=new TerminalManager;let _initializing=!1,_pluginRegisteredAt=0;const ED25519_PKCS8_PREFIX=Buffer.from("302e020100300506032b657004220420","hex");function agentKeyPairToIdentity(e){return{publicKeyBase64:()=>Buffer.from(e.publicKey).toString("base64"),sign:async r=>{const{createPrivateKey:o,sign:t}=await import("node:crypto"),a=o({key:32===e.privateKey.length?Buffer.concat([ED25519_PKCS8_PREFIX,Buffer.from(e.privateKey)]):Buffer.from(e.privateKey),format:"der",type:"pkcs8"});return new Uint8Array(t(null,Buffer.from(r),a))}}}let unhandledRejectionObserverInstalled=!1;function installUnhandledRejectionObserver(e,r,o){unhandledRejectionObserverInstalled||(unhandledRejectionObserverInstalled=!0,process.on("unhandledRejection",o=>{const t=o instanceof Error?o.message:String(o),a=o instanceof Error?o.stack:void 0;e.logger.error?.(`[beeos-claw] unhandledRejection: ${t}${a?`\n${a}`:""}`),r({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:t,error:{code:"unhandled_rejection",message:t,...a?{stack:a}:{}}})}),process.on("uncaughtException",o=>{const t=o instanceof Error?o.message:String(o);e.logger.error?.("[beeos-claw] uncaughtException:",o),r({component:"plugin",domain:"fatal",name:"uncaught_exception",severity:"error",summary:t,error:{code:"uncaught_exception",message:t}})}))}function initializePlugin(e){let r,o;try{r=e.runtime?.config?.loadConfig?.()}catch{}try{o=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:r})}catch(r){return e.logger.error?.("[beeos-claw] Failed to resolve config:",r),null}const t=o.config,a=buildThirdPartyChannelSet(r);a.size>0&&e.logger.info?.(`[beeos-claw] Third-party channels detected: ${[...a].join(", ")}`);const n=t.log.enabled?createObsEventWriter({dir:t.log.dir,redaction:{gatewayToken:t.gateway.token},logger:e.logger}):()=>{};n({component:"plugin",domain:"lifecycle",name:"lifecycle.startup",severity:"info",payload:{pluginVersion:PLUGIN_VERSION,pid:process.pid,nodeVersion:process.version,cwd:process.cwd()}}),n({component:"plugin",domain:"config",name:"config.resolved",severity:"info",payload:{config:t,sources:o.sources,openclawConfigPath:o.openclawConfigPath}});for(const r of o.validation)n({component:"plugin",domain:"config",name:"config.validation",severity:r.severity,summary:r.message,error:{code:r.code,message:r.message,nextSteps:r.nextSteps}}),"error"===r.severity?e.logger.error?.(`[beeos-claw] ${r.message}`):e.logger.warn?.(`[beeos-claw] ${r.message}`);if(o.validation.some(e=>"error"===e.severity)&&e.logger.error?.("[beeos-claw] Plugin has configuration errors — some features may not work."),t.keyFile)try{agentKeys=loadAgentKeyPair(t.keyFile);const r=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${r})`)}catch(r){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${r}`),e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no key pair — signed requests disabled)`)}else e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no keyFile configured)`);return agentKeys&&t.agentGatewayUrl&&(setUploadDeps({gatewayUrl:t.agentGatewayUrl,keys:agentKeys,logger:e.logger}),registerLocalFileExtractor(browserScreenshotExtractor),e.logger.info?.("[beeos-claw] Upload deps + browser screenshot extractor registered")),{cfg:t,writeObs:n,thirdPartyChannels:a,openclawConfig:r}}function registerUploadTools(e,r,o){const{cfg:t,writeObs:a}=r;cleanupUploadToolResultCache().catch(r=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(r)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(r,o,n){e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} params=${JSON.stringify(o)}`);const s=await executeUpload(o,t,agentKeys,n);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} ok=${s.ok}`),a({component:"upload-tool",domain:"tool",name:s.ok?"upload.success":"upload.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,fileCount:s.files?.length??0,error:s.error}}),(async o=>{try{await writeUploadToolResult(r,BEEOS_UPLOAD_TOOL_NAME,o),await cleanupUploadToolResultCache()}catch(o){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${r}: ${String(o)}`)}return o})(s)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,o){await handleFileStage(e,o,r.cfg)}}),e.registerHttpRoute({path:"/beeos/status",auth:"plugin",handler(e,r){handleStatusRequest(e,r,{publicKey:agentKeys?.publicKey??null,pluginVersion:PLUGIN_VERSION})}}),e.registerTool({name:BEEOS_LIST_FILES_TOOL_NAME,description:BEEOS_LIST_FILES_TOOL_DESCRIPTION,parameters:BEEOS_LIST_FILES_TOOL_SCHEMA,async execute(r,o,n){e.logger.debug?.(`[beeos_list_files] toolCallId=${r} params=${JSON.stringify(o)}`);const s=await executeListFiles(o,t,agentKeys,n);return a({component:"file-list-tool",domain:"tool",name:s.ok?"list_files.success":"list_files.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,fileCount:s.files?.length??0,error:s.error}}),s}}),e.registerTool({name:BEEOS_FETCH_FILE_TOOL_NAME,description:BEEOS_FETCH_FILE_TOOL_DESCRIPTION,parameters:BEEOS_FETCH_FILE_TOOL_SCHEMA,async execute(r,o,n){e.logger.debug?.(`[beeos_fetch_file] toolCallId=${r} params=${JSON.stringify(o)}`);const s=await executeFetchFile(o,t,agentKeys,{signal:n,resolvePath:e.resolvePath});return a({component:"file-fetch-tool",domain:"tool",name:s.ok?"fetch_file.success":"fetch_file.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,path:s.path,size:s.size,error:s.error}}),s}})}function registerServices(e,r,o){const{cfg:t,writeObs:a,thirdPartyChannels:n}=r,s=agentKeys?(e,r)=>agentFetch(e,agentKeys,r):void 0,i=agentKeys?(e,r)=>agentBoundFetch(e,agentKeys,r):void 0,l=agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):void 0,c=join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos");sharedMcpToolBridge??=new InProcessMcpToolBridge,!sharedMcpCredentialVault&&agentKeys&&l&&(sharedMcpCredentialVault=new LocalEncryptedCredentialReferenceVault(join(c,"mcp-credential-vault.json"),l,agentKeys.privateKey)),sharedMcpToolBridge.registerTools(e),!sharedRuntimeAuthority&&agentKeys&&t.agentGatewayUrl&&s&&i&&(sharedRuntimeAuthority=new RuntimeProductionAuthority({stateDirectory:c,agentGatewayUrl:t.agentGatewayUrl,agentFetch:s,boundAgentFetch:i,keys:agentKeys,logger:e.logger})),sharedSessionMutationCoordinator??=new SessionMutationCoordinator(sharedSessionWriteGate,new OpenClawSessionWriteQuiescence(e.logger,{agentId:t.gateway.agentId||"main"}),e.logger,{onRecoveryRequired:r=>{scheduleProcessRuntimeRestart(r,e.logger)}});const g=sharedSessionMutationCoordinator;sharedAcpServer||(sharedAcpServer=new ACPServer(t,e.logger,g,a,s,l,n,sharedTerminalManager,sharedRuntimeAuthority,void 0,sharedMcpToolBridge,sharedMcpCredentialVault,i));const d=sharedAcpServer;o.current=d;const u=sharedPromptRunStore,{enabled:p,url:m,principalId:f,a2aEnabled:y}=t.messageBus;let w;t.agentGatewayUrl&&agentKeys&&(w=createTokenProvider({agentGatewayUrl:t.agentGatewayUrl,identity:agentKeyPairToIdentity(agentKeys)}));const h=w?new MessageServiceRuntimeAuthenticatedFetch({tokenProvider:w,...t.messageBus.serviceUrl?{configuredServiceUrl:t.messageBus.serviceUrl}:{}}):void 0;e.registerService({id:"beeos-acp",async start(){const r=Date.now(),o=_pluginRegisteredAt?r-_pluginRegisteredAt:-1;e.logger.info?.(`[beeos-claw] beeos-acp service start() begin, sincePluginRegister=${o}ms, bridge.url=${t.bridge.url||"(empty)"}`);const a=await createRuntimeServiceProductionCandidate({...t.agentGatewayUrl&&s?{authorizationAdapter:new AgentGatewayGrantJwksReadinessAdapter({agentGatewayUrl:t.agentGatewayUrl})}:{},...h&&t.agentGatewayUrl&&i&&process.env.MESSAGE_RUNTIME_DELIVERY_KEY?.trim()?{transportAdapter:new RuntimeServiceTransportReadinessAdapter({agentGatewayUrl:t.agentGatewayUrl,messageService:h,boundAgentFetch:i,scopedDeliveryKey:process.env.MESSAGE_RUNTIME_DELIVERY_KEY.trim()})}:{},commandJournal:sharedRuntimeAuthority?.journal??new FileRuntimeCommandJournal(join(c,"runtime-command-journal.json")),poisonJournal:new FileRuntimePoisonDeliveryJournal(join(c,"runtime-poison-deliveries.json")),onConsumerDeliveryError:r=>e.logger.error?.("[runtime-service] delivery worker failed before acknowledgement; delivery retained for retry",r),onConsumerRequestFailure:r=>e.logger.error?.(`[runtime-service] request failed operationId=${r.operationId} method=${r.method}: ${r.error instanceof Error?r.error.stack??r.error.message:String(r.error)}`)});if(sharedRuntimeAuthority?.setRuntimeServiceMethods("ready"===a.status?a.serviceMethods:[]),sharedRuntimeAuthority?.setRuntimeResourceBindingContractRevision("ready"===a.status?a.resourceBindingContractRevision:void 0),d.setRuntimeRpcConsumerFactory("ready"===a.status?a.factory:void 0),"unavailable"===a.status?e.logger.warn?.("[runtime-service] capability fenced: %s",a.blockers.map(e=>e.id).join(",")):a.capabilityBlockers.length&&e.logger.warn?.("[runtime-service] method capability fenced: %s",a.capabilityBlockers.map(e=>e.method).join(",")),!t.bridge.url&&t.agentGatewayUrl&&s){const r=Date.now(),o=await discoverBridgeFromGW(t.agentGatewayUrl,s);if(e.logger.info?.(`[beeos-claw] bridge discovery took ${Date.now()-r}ms result=${o?.url??"null"}`),o)t.bridge.url=o.url,writeDiscoveryCache({bridge:{url:o.url,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${o.url}`);else{const r=readDiscoveryCache();r?.bridge?.url?(t.bridge.url=r.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${r.bridge.url}`)):e.logger.debug?.("[beeos-claw] Bridge URL not available (no GW, no cache) — bridge disabled")}}await d.start(t.bridge.url||void 0),e.logger.info?.(`[beeos-claw] ACP Server started (total service start: ${Date.now()-r}ms)`)},async stop(){await d.stop(),e.logger.info?.("[beeos-claw] ACP Server stopped")}});const b=p&&(w||t.messageBus.serviceUrl),_=b?d.getMessageClient():null;if(b&&_){_.on("disconnect",r=>{e.logger.warn?.("[beeos-claw] message bus disconnected (code=%s, reason=%s) — SDK will auto-reconnect",r?.code??"?",r?.reason??"?")}),_.on("error",r=>{e.logger.warn?.(`[beeos-claw] message bus error: ${r instanceof Error?r.message:String(r)}`)});let r=f||process.env.BEEOS_PRINCIPAL_ID||"";const o={serviceUrl:t.messageBus.serviceUrl||"https://msg.invalid",centrifugoUrl:m,tokenProvider:w,logger:e.logger},n=new Map;let s,i=!1;const l=async()=>{if(r||!w)return r;try{const o=await w("");r=o.identityId||"",r&&e.logger.info?.("[beeos-claw] Message bus principal resolved from Agent Gateway token: %s",r)}catch(r){e.logger.warn?.("[beeos-claw] Failed to resolve message bus principal from Agent Gateway token: %s",r instanceof Error?r.message:String(r))}return r},c=()=>{if(r)return[{localAgentId:d.getLocalAgentId(),principalId:r}];const e=d.getPlatformAgentMappings().map(({localAgentId:e,platformAgent:r})=>({localAgentId:e,principalId:r.instanceId||""})).filter(e=>e.principalId);if(e.length>0)return e;const o=d.getDefaultPlatformAgent()?.instanceId||"";return o?[{localAgentId:d.getLocalAgentId(),principalId:o}]:[]},p=e=>({getLocalAgentId:()=>e.localAgentId,getDefaultPlatformAgent:()=>({instanceId:e.principalId}),ensureImInboundSession:(e,r,o)=>{d.ensureImInboundSession(e,r,o)},chatAbort:(e,r)=>d.chatAbort(e,r)}),y=async(r,s)=>{if(n.has(r.principalId))return;const i=s?_:new MessageClient(o);s&&d.setMessageClient(o,r.principalId);const l=createImInboundHandler({runtime:e.runtime,pluginConfig:e.pluginConfig,client:i,localServer:p(r),promptRunStore:u,sessionMutationCoordinator:g,logger:e.logger,writeObs:a,principalIdOverride:r.principalId,promptTimeoutMs:t.promptTimeoutMs,activeRunAuthority:d.getActiveRunAuthority()}),c=new MessageServiceRecoveryScanner({client:i,imInbound:l,logger:e.logger});i.on("connect",()=>{c.trigger("reconnect"),e.logger.info?.("[beeos-claw] MessageClient connected (localAgentId=%s, principalId=%s, via=%s)",r.localAgentId,r.principalId,w?"gateway":"direct")}),i.on("message",r=>{(async()=>{"chat_message"===r.type?await l.onChatMessage(r):"chat_cancel"===r.type&&await l.onChatCancel(r)})().catch(r=>{e.logger.warn?.(`[beeos-claw] message handler failed: ${r instanceof Error?r.message:String(r)}`)})}),n.set(r.principalId,{client:i,scanner:c,primary:s});try{await i.connect({identityId:r.principalId}),c.start(r.principalId)}catch(e){throw n.delete(r.principalId),c.stop(),s||await i.disconnect().catch(()=>{}),e}},h=async r=>{if(i)if(s)await s;else{s=(async()=>{const o=c();if(0===o.length)return void e.logger.warn?.("[beeos-claw] No principalId resolved, message bus disabled");const t=new Set(o.map(e=>e.principalId));let a=!1;for(const[o,s]of n)t.has(o)||(s.primary&&(a=!0),s.scanner.stop(),await s.client.disconnect(),n.delete(o),e.logger.info?.("[beeos-claw] MessageClient disconnected stale identity principalId=%s reason=%s",o,r));if(a&&n.size>0)for(const[o,t]of n)t.scanner.stop(),await t.client.disconnect(),n.delete(o),e.logger.info?.("[beeos-claw] MessageClient disconnected for primary rebalance principalId=%s reason=%s",o,r);let s=[...n.values()].some(e=>e.primary);for(const e of o){const r=!s;await y(e,r),r&&(s=!0)}})();try{await s}finally{s=void 0}}};e.registerService({id:"beeos-message-bus",async start(){if(i=!0,d.onAgentMappingUpdated(()=>{h("agent-mapping-updated").catch(r=>{e.logger.error?.("[beeos-claw] Message bus mapping sync failed:",r)})}),await l(),!r)try{await d.waitForAgentMapping()}catch(r){e.logger.warn?.("[beeos-claw] Failed to resolve principalId from agent sync:",r)}await h("startup")},async stop(){i=!1;const r=[...n.values()];n.clear();for(const e of r)e.scanner.stop(),await e.client.disconnect();e.logger.info?.("[beeos-claw] MessageClient disconnected")}})}else p?e.logger.warn?.("[beeos-claw] Message bus enabled but MESSAGE_SERVICE_URL not set — disabled"):e.logger.info?.("[beeos-claw] Message bus disabled (enabled=%s)",p);if(!1!==y&&t.agentGatewayUrl){const r=e=>d.getGatewaySessionKeyForToolCall(e),o=e=>d.getMentionsForToolCall(e),a=(e,r)=>{d.broadcastNotification(e,r)},n=new TaskHandleRegistry;registerA2ATools(e,{agentGatewayUrl:t.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},n,r,o,a,()=>d?.getDefaultPlatformAgent()?.instanceId),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(t.canvasEnabled){sharedCanvasState||(sharedCanvasState=new CanvasStateManager);const r=sharedCanvasState;if(t.agentGatewayUrl&&agentKeys){let o;r.setLogger(e.logger);{const e=t.agentGatewayUrl,r=agentKeys;o=async()=>{const o=await agentFetch(`${e}/api/v1/canvas/token`,r,{method:"POST"});if(!o.ok){const e=await o.text().catch(()=>"");throw new Error(`Canvas token returned ${o.status}: ${e}`)}return o.json()}}const a=a=>{const n=new CanvasRelayClient({canvasRelayUrl:t.canvasRelayUrl,agentKeys:agentKeys,agentId:a.id,displayName:a.displayName,avatarUrl:a.avatarUrl,tokenProvider:o,onReconnect:()=>d.onRelayReconnected(),logger:e.logger});n.connect(),r.setRelayClient(n),r.setAgentId(a.id),d.wireRelayActionHandler(),d.flushPendingCanvasLinks(),e.logger.info?.(`[beeos-claw] Canvas Relay multiplexed client connected (agentId=${a.id}, name=${a.name??"none"})`)};e.registerService({id:"beeos-canvas-relay",async start(){try{await d.waitForAgentMapping();const r=d.getDefaultPlatformAgent();if(r?.id)return void a(r);e.logger.info?.("[beeos-claw] Canvas Relay deferred: waiting for late agent sync..."),d.onceAgentMappingReady(()=>{const r=d.getDefaultPlatformAgent();if(r?.id)try{a(r)}catch(r){e.logger.warn?.(`[beeos-claw] Deferred Canvas Relay start failed: ${r}`)}else e.logger.warn?.("[beeos-claw] Agent mapping ready but no platform agent; Canvas Relay stays disabled")})}catch(r){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${r}`)}},stop(){r.getRelayClient()?.disconnect()}}),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const o={stateManager:r,logger:e.logger,writeObs:e=>a(e),isCanvasActive:e=>!!e&&d.isCanvasActiveForGatewayKey(e),getActiveSessionId:e=>d.getSessionIdForToolCall(e),getPlatformAgentId:e=>d.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>d.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>d.waitForAgentMapping()},s=new FormSchemaRegistry,i=buildCronSchemas(n,{capMs:t.cronMaxDurationCapMs});s.register(i.create),s.register(i.update);const l=new ResourceDescriptorRegistry;l.register(CRON_RESOURCE);const c=e=>async(r,o,t)=>(d.preMapToolCallSession(r),e(r,o));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:c(createRenderUiHandler(o))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:c(createCloseUiHandler(o))}),d.setCanvasState(r,{schemaRegistry:s,descriptorRegistry:l});const g=d.getFormBuilder();g&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(s),parameters:RENDER_FORM_TOOL_SCHEMA,execute:c(createRenderFormHandler({registry:s,builder:g,logger:e.logger}))});const u=d.getResourceRenderer();if(u){generateActionTools(l,e,async(e,r)=>d.gatewayRequest(e,r),u,e.logger)}const p=`\n\nTool selection guidance:\n- browser tool: Use when the user wants to OPEN, VISIT, NAVIGATE to a webpage, take SCREENSHOTS, interact with elements (click, type), or visually inspect pages.\n- beeos_search: Use ONLY for text-based information retrieval (fact-finding, research queries). Never for page interaction or screenshots.\n- beeos_fetch: Use ONLY to extract article text from a known URL. Never for visual content or screenshots.\n- ${BEEOS_FILE_RESOURCE_ROUTING_RULE}\nWhen the user says "open", "visit", "go to", "screenshot", or mentions seeing/viewing a webpage, ALWAYS prefer the browser tool.`,m=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),f=256,y=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,r)=>{if(r?.sessionKey){if(y.size>=f&&!y.has(r.sessionKey)){const e=y.keys().next().value;void 0!==e&&y.delete(e)}y.set(r.sessionKey,r.channelId)}if(isThirdPartyChannel(n,{sessionKey:r?.sessionKey,channelId:r?.channelId}))return{appendSystemContext:'This conversation is in a third-party channel (Lark/Telegram/Discord/Slack); Canvas surfaces are not visible here. Do NOT use render_ui, close_ui, or render_form. Use built-in tools directly (e.g. the `cron` tool with `action:"add"` for scheduled tasks) and respond with plain text.'};if(!r?.sessionKey)return{appendSystemContext:p};return d.isCanvasActiveForGatewayKey(r.sessionKey)?{appendSystemContext:CANVAS_SYSTEM_PROMPT+p}:{appendSystemContext:"Canvas is closed by user. Do NOT use render_ui, close_ui, or render_form. Respond with plain text only."+p}}),e.logger.debug?.("[beeos-claw] Canvas system prompt hook registered: before_prompt_build")}catch{e.logger.debug?.("[beeos-claw] before_prompt_build hook not supported")}try{e.on("before_tool_call",(e,r)=>{if(r?.sessionKey&&d.setPendingToolCallSessionKey(r.sessionKey),!m.has(e.toolName))return;const o=r?.sessionKey?y.get(r.sessionKey):void 0;return isThirdPartyChannel(n,{sessionKey:r?.sessionKey,channelId:o})?{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."}:r?.sessionKey?d.isCanvasActiveForGatewayKey(r.sessionKey)?void 0:{block:!0,blockReason:"Canvas is closed by user. Respond with plain text instead."}:{block:!0,blockReason:"Canvas session unknown. Respond with plain text instead."}}),e.logger.debug?.("[beeos-claw] Canvas tool guard registered: before_tool_call")}catch{e.logger.debug?.("[beeos-claw] before_tool_call hook not supported")}}e.logger.info?.("[beeos-claw] Canvas tools registered (render_ui, close_ui, render_form + auto-action tools)")}else e.logger.info?.("[beeos-claw] Canvas disabled by config (canvasEnabled=false)");if(t.bridge.shell.enabled&&!0===t.legacyTerminalWsEnabled){const r=createTerminalWebSocketService({config:{port:t.terminalWsPort,token:agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):"",allowedOrigins:t.terminalWsAllowedOrigins},terminalMgr:sharedTerminalManager,logger:e.logger,writeObs:a});e.registerService({id:"beeos-terminal-ws",start(){r.start(),e.logger.info?.(`[beeos-claw] Terminal WS started on port ${t.terminalWsPort}`)},stop(){r.stop(),e.logger.info?.("[beeos-claw] Terminal WS stopped")}})}else t.bridge.shell.enabled?e.logger.info?.("[beeos-claw] Legacy static-token Terminal WS disabled (default)"):e.logger.info?.("[beeos-claw] Terminal/shell disabled by config (bridge.shell.enabled=false)");if(r.openclawConfig){const o=mirrorOpenClawConfig(r.openclawConfig);o.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${o.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${o.reason}`)}}const CAPABILITY_REGISTRATION_MODES=new Set(["full","discovery","tool-discovery"]),plugin={id:"beeos-claw",name:"beeos-claw",description:"BeeOS platform bridge plugin for OpenClaw. Provides file upload/download, ACP bridge, and observability.",version:PLUGIN_VERSION,register(e){if(!_initializing)if(!e.registrationMode||CAPABILITY_REGISTRATION_MODES.has(e.registrationMode)){_initializing=!0;try{if(!hasRuntimeReplyApi(e.runtime))throw e.logger.error?.("[beeos-claw] FATAL: OpenClaw runtime.channel.reply.dispatchReplyFromConfig not exposed. Upgrade OpenClaw or revert beeos-claw."),new Error("openclaw_runtime_reply_api_unavailable");const r=initializePlugin(e);if(!r)return;const o={current:null};installUnhandledRejectionObserver(e,r.writeObs,o),registerUploadTools(e,r,o),registerArouterMediaProviders(e),registerServices(e,r,o),_pluginRegisteredAt=Date.now(),e.logger.info?.("[beeos-claw] Plugin registered successfully")}finally{_initializing=!1}}else e.logger.debug?.(`[beeos-claw] Skipping registration (mode=${e.registrationMode})`)}};export default plugin;
@@ -1 +1 @@
1
- import{createHash,randomUUID}from"node:crypto";import{mkdir,open,readFile,readdir,rename,unlink}from"node:fs/promises";import{dirname,join}from"node:path";import{canonicalizeJcs}from"@beeos-ai/beeos-types/runtime";const WATERMARK_KEY="\0authority-watermark";export class MonotonicJournalSegments{baseFile;fault;directory;constructor(e,o={}){this.baseFile=e,this.fault=o,this.directory=`${e}.segments`}async reduce(e=new Map){let o,t=new Map(e);try{o=await readdir(this.directory)}catch(e){if("ENOENT"===e.code)return t;throw e}const i=[];for(const e of o.sort())if(e.endsWith(".json"))try{i.push(parseEnvelope(await readFile(join(this.directory,e),"utf8")))}catch(e){if("ENOENT"!==e.code)throw e}const r=i.filter(e=>e.key===WATERMARK_KEY).reduce((e,o)=>BigInt(o.publisherEpoch)>e?BigInt(o.publisherEpoch):e,0n);r>0n&&(t=new Map);const n=new Map;for(const e of i){if(e.key===WATERMARK_KEY||0n===r||BigInt(e.publisherEpoch)!==r)continue;const o=n.get(e.key);(!o||compareEnvelope(e,o)>0)&&n.set(e.key,e)}for(const[e,o]of n){const i=t.get(e);i&&compareCursor(o.logicalRevision,o.publisherEpoch,i.logicalRevision,i.publisherEpoch)<0||(o.tombstone?t.delete(e):t.set(e,{value:o.value,logicalRevision:o.logicalRevision,publisherEpoch:o.publisherEpoch}))}return t}async authorityFloor(){let e;try{e=await readdir(this.directory)}catch(e){if("ENOENT"===e.code)return 0n;throw e}let o=0n;for(const t of e){if(!t.endsWith(".json"))continue;let e;try{e=parseEnvelope(await readFile(join(this.directory,t),"utf8"))}catch(e){if("ENOENT"===e.code)continue;throw e}e.key===WATERMARK_KEY&&BigInt(e.publisherEpoch)>o&&(o=BigInt(e.publisherEpoch))}return o}async publish(e,o,t){await mkdir(this.directory,{recursive:!0,mode:448});const i=await this.authorityFloor(),r=t??i;if(i!==r||BigInt(e)<=i)throw new Error("monotonic journal publisher is stale or non-successor");for(const t of o){if(!t.key||!/^(0|[1-9][0-9]*)$/.test(t.logicalRevision)||!/^[1-9][0-9]*$/.test(e))throw new Error("invalid monotonic journal cursor");await this.writeEnvelope(e,t)}if(await syncDirectory(this.directory),await(this.fault.beforeWatermark?.()),await this.authorityFloor()!==r)throw new Error("monotonic journal authority changed before watermark");await this.writeEnvelope(e,{key:WATERMARK_KEY,logicalRevision:"0",value:null}),await syncDirectory(this.directory),await syncDirectory(dirname(this.directory))}async prune(){let e;try{e=(await readdir(this.directory)).filter(e=>e.endsWith(".json")).sort()}catch(e){if("ENOENT"===e.code)return 0;throw e}const o=[];for(const t of e)try{o.push({name:t,envelope:parseEnvelope(await readFile(join(this.directory,t),"utf8"))})}catch(e){if("ENOENT"!==e.code)throw e}const t=new Map,i=o.filter(e=>e.envelope.key===WATERMARK_KEY).reduce((e,o)=>BigInt(o.envelope.publisherEpoch)>e?BigInt(o.envelope.publisherEpoch):e,0n);for(const e of o){if(e.envelope.key!==WATERMARK_KEY&&(0n===i||BigInt(e.envelope.publisherEpoch)!==i))continue;const o=t.get(e.envelope.key);(!o||compareEnvelope(e.envelope,o.envelope)>0||0===compareEnvelope(e.envelope,o.envelope)&&e.name>o.name)&&t.set(e.envelope.key,e)}let r=0;for(const e of o){const o=BigInt(e.envelope.publisherEpoch),n=e.envelope.key!==WATERMARK_KEY&&i>0n&&o<i,a=(e.envelope.key===WATERMARK_KEY||o===i)&&t.get(e.envelope.key)?.name!==e.name;(n||a)&&(await unlink(join(this.directory,e.name)).catch(e=>{if("ENOENT"!==e.code)throw e}),r+=1)}return r&&await syncDirectory(this.directory),r}async writeEnvelope(e,o){const t={schemaVersion:1,key:o.key,logicalRevision:o.logicalRevision,publisherEpoch:e,tombstone:!0===o.tombstone,...!0===o.tombstone?{}:{value:o.value}},i=digest(t),r={...t,payloadHash:i},n=`${digest(o.key).slice(0,20)}.${o.logicalRevision}.${e}.${i}`,a=join(this.directory,`${n}.json`),s=join(this.directory,`.${n}.${process.pid}.${randomUUID()}.tmp`),c=await open(s,"wx",384);try{await c.writeFile(`${JSON.stringify(r)}\n`,"utf8"),await c.sync()}finally{await c.close()}try{await rename(s,a)}catch(e){if(await unlink(s).catch(()=>{}),"EEXIST"!==e.code)throw e}}}function parseEnvelope(e){let o;try{o=JSON.parse(e)}catch{throw new Error("monotonic journal segment is corrupt")}if(1!==o.schemaVersion||"string"!=typeof o.key||!o.key||!/^(0|[1-9][0-9]*)$/.test(o.logicalRevision)||!/^[1-9][0-9]*$/.test(o.publisherEpoch)||"boolean"!=typeof o.tombstone||"string"!=typeof o.payloadHash||(o.tombstone?void 0!==o.value:void 0===o.value))throw new Error("monotonic journal segment is invalid");if(digest({schemaVersion:1,key:o.key,logicalRevision:o.logicalRevision,publisherEpoch:o.publisherEpoch,tombstone:o.tombstone,...o.tombstone?{}:{value:o.value}})!==o.payloadHash)throw new Error("monotonic journal segment hash mismatch");return o}function compareEnvelope(e,o){const t=compareCursor(e.logicalRevision,e.publisherEpoch,o.logicalRevision,o.publisherEpoch);return 0!==t?t:e.payloadHash.localeCompare(o.payloadHash)}function compareCursor(e,o,t,i){const r=BigInt(o)-BigInt(i);if(0n!==r)return r>0n?1:-1;const n=BigInt(e)-BigInt(t);return 0n===n?0:n>0n?1:-1}function digest(e){return createHash("sha256").update(canonicalizeJcs(e)).digest("hex")}async function syncDirectory(e){const o=await open(e,"r");try{await o.sync()}finally{await o.close()}}
1
+ import{createHash,randomUUID}from"node:crypto";import{mkdir,open,readFile,readdir,rename,unlink}from"node:fs/promises";import{dirname,join}from"node:path";import{canonicalizeJcs}from"@beeos-ai/beeos-types/runtime";const WATERMARK_KEY="\0authority-watermark",SNAPSHOT_NAME=/^([1-9][0-9]*)\.([0-9a-f]{20})\.snapshot$/;export class MonotonicJournalSegments{baseFile;fault;directory;constructor(e,o={}){this.baseFile=e,this.fault=o,this.directory=`${e}.segments`}async reduce(e=new Map){let o,t=new Map(e);try{o=await readdir(this.directory)}catch(e){if("ENOENT"===e.code)return t;throw e}const n=await this.readAuthoritativeSnapshot(o),r=await this.readLegacyEnvelopes(o),i=legacyAuthorityEpoch(r);if(n&&BigInt(n.publisherEpoch)>=i){t=new Map;for(const e of n.entries)e.tombstone||t.set(e.key,{value:e.value,logicalRevision:e.logicalRevision,publisherEpoch:n.publisherEpoch});return t}i>0n&&(t=new Map);const a=new Map;for(const e of r){if(e.key===WATERMARK_KEY||0n===i||BigInt(e.publisherEpoch)!==i)continue;const o=a.get(e.key);(!o||compareEnvelope(e,o)>0)&&a.set(e.key,e)}for(const[e,o]of a){const n=t.get(e);n&&compareCursor(o.logicalRevision,o.publisherEpoch,n.logicalRevision,n.publisherEpoch)<0||(o.tombstone?t.delete(e):t.set(e,{value:o.value,logicalRevision:o.logicalRevision,publisherEpoch:o.publisherEpoch}))}return t}async authorityFloor(){let e;try{e=await readdir(this.directory)}catch(e){if("ENOENT"===e.code)return 0n;throw e}const o=highestSnapshotEpoch(e),t=legacyAuthorityEpoch(await this.readLegacyEnvelopes(e));return o>t?o:t}async publish(e,o,t){await mkdir(this.directory,{recursive:!0,mode:448});const n=await this.authorityFloor(),r=t??n;if(n!==r||BigInt(e)<=n)throw new Error("monotonic journal publisher is stale or non-successor");if(!/^[1-9][0-9]*$/.test(e))throw new Error("invalid monotonic journal cursor");const i={schemaVersion:2,publisherEpoch:e,entries:o.map(e=>{if(!e.key||!/^(0|[1-9][0-9]*)$/.test(e.logicalRevision))throw new Error("invalid monotonic journal cursor");return{key:e.key,logicalRevision:e.logicalRevision,tombstone:!0===e.tombstone,...!0===e.tombstone?{}:{value:e.value}}})},a=digest(i),s={...i,payloadHash:a},c=`${e}.${a.slice(0,20)}`,l=join(this.directory,`${c}.snapshot`),h=join(this.directory,`.${c}.${process.pid}.${randomUUID()}.tmp`),p=await open(h,"wx",384);let u=!1;try{if(await p.writeFile(`${JSON.stringify(s)}\n`,"utf8"),await p.sync(),await p.close(),await(this.fault.beforeWatermark?.()),await this.authorityFloor()!==r)throw new Error("monotonic journal authority changed before watermark");try{await rename(h,l)}catch(e){if("EEXIST"!==e.code)throw e;await unlink(h).catch(()=>{})}u=!0,await syncDirectory(this.directory),await syncDirectory(dirname(this.directory))}finally{u||(await p.close().catch(()=>{}),await unlink(h).catch(()=>{}))}}async prune(){let e;try{e=await readdir(this.directory)}catch(e){if("ENOENT"===e.code)return 0;throw e}const o=highestSnapshotEpoch(e),t=legacyAuthorityEpoch(await this.readLegacyEnvelopes(e));let n=0;const r=async e=>{await unlink(join(this.directory,e)).catch(e=>{if("ENOENT"!==e.code)throw e}),n+=1};if(o>0n&&o>=t){const o=e.filter(e=>SNAPSHOT_NAME.test(e)).sort((e,o)=>compareSnapshotName(e,o)).at(-1);for(const t of e.filter(e=>SNAPSHOT_NAME.test(e)&&e!==o))await r(t);for(const o of e.filter(e=>e.endsWith(".json")))await r(o)}else{for(const o of e.filter(e=>SNAPSHOT_NAME.test(e))){const[,e]=SNAPSHOT_NAME.exec(o);BigInt(e)<t&&await r(o)}n+=await this.pruneLegacyEnvelopes(e,t)}return n&&await syncDirectory(this.directory),n}async readAuthoritativeSnapshot(e){const o=e.filter(e=>SNAPSHOT_NAME.test(e)).sort((e,o)=>compareSnapshotName(o,e));for(const e of o){let o;try{o=await readFile(join(this.directory,e),"utf8")}catch(e){if("ENOENT"===e.code)continue;throw e}return parseSnapshot(o,e)}return null}async readLegacyEnvelopes(e){const o=[];for(const t of[...e].sort())if(t.endsWith(".json"))try{o.push(parseEnvelope(await readFile(join(this.directory,t),"utf8")))}catch(e){if("ENOENT"!==e.code)throw e}return o}async pruneLegacyEnvelopes(e,o){const t=[];for(const o of[...e].filter(e=>e.endsWith(".json")).sort())try{t.push({name:o,envelope:parseEnvelope(await readFile(join(this.directory,o),"utf8"))})}catch(e){if("ENOENT"!==e.code)throw e}const n=new Map;for(const e of t){if(e.envelope.key!==WATERMARK_KEY&&(0n===o||BigInt(e.envelope.publisherEpoch)!==o))continue;const t=n.get(e.envelope.key);(!t||compareEnvelope(e.envelope,t.envelope)>0||0===compareEnvelope(e.envelope,t.envelope)&&e.name>t.name)&&n.set(e.envelope.key,e)}let r=0;for(const e of t){const t=BigInt(e.envelope.publisherEpoch),i=e.envelope.key!==WATERMARK_KEY&&o>0n&&t<o,a=(e.envelope.key===WATERMARK_KEY||t===o)&&n.get(e.envelope.key)?.name!==e.name;(i||a)&&(await unlink(join(this.directory,e.name)).catch(e=>{if("ENOENT"!==e.code)throw e}),r+=1)}return r}}function legacyAuthorityEpoch(e){return e.filter(e=>e.key===WATERMARK_KEY).reduce((e,o)=>BigInt(o.publisherEpoch)>e?BigInt(o.publisherEpoch):e,0n)}function highestSnapshotEpoch(e){let o=0n;for(const t of e){const e=SNAPSHOT_NAME.exec(t);e&&BigInt(e[1])>o&&(o=BigInt(e[1]))}return o}function compareSnapshotName(e,o){const[,t,n]=SNAPSHOT_NAME.exec(e),[,r,i]=SNAPSHOT_NAME.exec(o),a=BigInt(t)-BigInt(r);return 0n!==a?a>0n?1:-1:n.localeCompare(i)}function parseSnapshot(e,o){let t;try{t=JSON.parse(e)}catch{throw new Error("monotonic journal snapshot is corrupt")}if(2!==t.schemaVersion||!/^[1-9][0-9]*$/.test(t.publisherEpoch)||!Array.isArray(t.entries)||"string"!=typeof t.payloadHash||!t.entries.every(e=>e&&"string"==typeof e.key&&e.key&&/^(0|[1-9][0-9]*)$/.test(e.logicalRevision)&&"boolean"==typeof e.tombstone&&(e.tombstone?void 0===e.value:void 0!==e.value)))throw new Error("monotonic journal snapshot is invalid");const n=SNAPSHOT_NAME.exec(o);if(!n||n[1]!==t.publisherEpoch||n[2]!==t.payloadHash.slice(0,20))throw new Error("monotonic journal snapshot identity mismatch");if(digest({schemaVersion:2,publisherEpoch:t.publisherEpoch,entries:t.entries})!==t.payloadHash)throw new Error("monotonic journal snapshot hash mismatch");return t}function parseEnvelope(e){let o;try{o=JSON.parse(e)}catch{throw new Error("monotonic journal segment is corrupt")}if(1!==o.schemaVersion||"string"!=typeof o.key||!o.key||!/^(0|[1-9][0-9]*)$/.test(o.logicalRevision)||!/^[1-9][0-9]*$/.test(o.publisherEpoch)||"boolean"!=typeof o.tombstone||"string"!=typeof o.payloadHash||(o.tombstone?void 0!==o.value:void 0===o.value))throw new Error("monotonic journal segment is invalid");if(digest({schemaVersion:1,key:o.key,logicalRevision:o.logicalRevision,publisherEpoch:o.publisherEpoch,tombstone:o.tombstone,...o.tombstone?{}:{value:o.value}})!==o.payloadHash)throw new Error("monotonic journal segment hash mismatch");return o}function compareEnvelope(e,o){const t=compareCursor(e.logicalRevision,e.publisherEpoch,o.logicalRevision,o.publisherEpoch);return 0!==t?t:e.payloadHash.localeCompare(o.payloadHash)}function compareCursor(e,o,t,n){const r=BigInt(o)-BigInt(n);if(0n!==r)return r>0n?1:-1;const i=BigInt(e)-BigInt(t);return 0n===i?0:i>0n?1:-1}function digest(e){return createHash("sha256").update(canonicalizeJcs(e)).digest("hex")}async function syncDirectory(e){const o=await open(e,"r");try{await o.sync()}finally{await o.close()}}
@@ -1 +1 @@
1
- import{RUNTIME_ERROR_DESCRIPTORS,RUNTIME_HASH_DOMAINS,RUNTIME_INVOCATION_PARITY_ENTRIES,RUNTIME_INVOCATION_RECOVERY_CLASSES,SERVICE_CANONICAL_COMMAND_NAMES,mapCanonicalResultToService}from"@beeos-ai/beeos-types/runtime";import{reduceRuntimeRecoveryPolicy}from"@beeos-ai/beeos-types/runtime/internal";import{verifyAndCreateMessageServiceCoreExecutionAttempt}from"./core-execution-attempt-context.js";import{awaitRuntimeProjection,hasEffectReceiptHandoff,RuntimeCommandError}from"./runtime-command-dispatcher.js";import{RUNTIME_MESSAGE_TYPES,runtimeMessageBase,runtimeMessageHash,semanticEqual,validateRuntimeCancelEnvelope,validateRuntimeRequestEnvelope}from"./runtime-message-channel-contract.js";import{RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{RuntimeTransportFailurePolicy}from"./runtime-transport-failure-policy.js";export class MsRuntimeMessageChannelAdapter{dispatcher;channel;verifier;ports;journal;getLease;options;controller;running;workers=new Map;poisonJournal;renewTransportFailurePolicy=new RuntimeTransportFailurePolicy;receiptHistory=this;constructor(e,t,o,n,a,r,i){if(this.dispatcher=e,this.channel=t,this.verifier=o,this.ports=n,this.journal=a,this.getLease=r,this.options=i,!i.poisonJournal)throw new Error("durable poison journal is required");if(this.poisonJournal=i.poisonJournal,!Number.isSafeInteger(i.poisonDeliveryLimit??3)||(i.poisonDeliveryLimit??3)<=0)throw new Error("poison delivery limit must be a positive integer")}start(){if(this.running)return;const e=new AbortController;this.controller=e;const t=this.loop(e.signal).catch(t=>{e.signal.aborted||this.observeDeliveryError(t)}).finally(()=>{this.running===t&&(this.running=void 0)});this.running=t}async stop(e){this.controller?.abort();for(const e of this.workers.values())e.cancel?.();this.running&&await abortable(this.running,e)}async readOperationHistory(e){const t=this.getLease();if(!t)return{status:"unavailable"};try{return await this.channel.history({operationId:e.operationId,lease:t})}catch{return{status:"unavailable"}}}async loop(e){let t;(()=>{t||(t=setInterval(()=>{this.renewActive().catch(e=>this.observeDeliveryError(e))},this.options.renewIntervalMs??2e4),t.unref?.())})();try{for(;!e.aborted;){const t=this.getLease();if(!t){await delay(this.options.idleDelayMs??500,e);continue}let o;try{o=await this.channel.read({maxCount:this.options.readCount??32,blockMs:this.options.blockMs??5e3,lease:t,signal:e})}catch(t){if(e.aborted)return;this.observeDeliveryError(t),await delay(this.options.idleDelayMs??500,e);continue}for(const n of o){if(e.aborted)break;if(n.message.type===RUNTIME_MESSAGE_TYPES.cancel){await this.handleCancel(n,t).catch(e=>this.observeDeliveryError(e));continue}const o=n.message.channel_id;if(this.workers.has(o))continue;const a={deliveryId:n.deliveryId,claimedLease:t,completion:Promise.resolve()};a.completion=this.handleRequest(n,t,e,e=>{a.cancel=e}).catch(e=>this.observeDeliveryError(e)).finally(()=>{this.workers.delete(o)}),this.workers.set(o,a)}}}finally{t&&clearInterval(t),await Promise.allSettled([...this.workers.values()].map(e=>e.completion))}}observeDeliveryError(e){try{this.options.onDeliveryError?.(e)}catch{}}async renewActive(){const e=this.getLease();if(!e)return;const t=[];for(const o of this.workers.values())sameLeaseBinding(o.claimedLease,e)?t.push(o.deliveryId):o.cancel?.();if(!t.length)return;let o;try{o=await this.channel.renew({deliveryIds:t,lease:e})}catch(e){if(this.observeDeliveryError(e),this.renewTransportFailurePolicy.recordFailure()){const e=new Set(t);for(const t of this.workers.values())e.has(t.deliveryId)&&t.cancel?.()}return}if(this.renewTransportFailurePolicy.recordSuccess(),o.notPending.length){const e=new Set(o.notPending);for(const t of this.workers.values())e.has(t.deliveryId)&&t.cancel?.()}}async handleCancel(e,t){const o=validateRuntimeCancelEnvelope(e.message);if(!o.ok)return void await this.handlePoison(e,`invalid_cancel:${o.reason}`,t);const n=o.envelope.payload,a=await this.channel.history({operationId:n.operationId,lease:t});if("found"===a.status&&a.snapshot.terminal&&"final_message"===a.snapshot.terminalSource)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});if("not_found"===a.status||"expired"===a.status)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});const r=a.snapshot.request.payload;r.operationId===n.operationId&&r.requestMessageId===n.requestMessageId&&r.requestHash===n.requestHash&&r.dispatchEpoch===n.dispatchEpoch&&r.method===n.method?(await this.journal.observeCancellation({operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,eventId:n.eventId}),this.workers.get(n.operationId)?.cancel?.(),await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t})):await this.handlePoison(e,"cancel_history_mismatch",t)}async handleRequest(e,t,o,n){const a=validateRuntimeRequestEnvelope(e.message);if(!a.ok)return void await this.handlePoison(e,`invalid_request:${a.reason}`,t);const r=a.envelope.payload,i=e.executionGrant,s=new AbortController;n(()=>s.abort());const c=async()=>await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:this.currentLease(t)}),d=await this.channel.history({operationId:r.operationId,lease:t});if("not_found"===d.status||"expired"===d.status)return void await c();if(d.snapshot.terminal&&"final_message"===d.snapshot.terminalSource)return void await c();if(this.options.allowedMethods&&!this.options.allowedMethods.has(r.method))return await this.reject(r,"unsupported_method",t),void await c();if("deadline"===d.snapshot.terminalSource||Date.parse(r.executionDeadlineAt)<=Date.now()||!d.snapshot.started&&Date.parse(r.claimDeadlineAt)<=Date.now())return await this.reject(r,"execution_deadline_exceeded",t),void await c();const l=await this.journal.getCancellation(r.operationId),u=d.snapshot.cancelRequested||null!==l;if(u&&"queued"===d.snapshot.status)return await this.reject(r,"cancelled_before_dispatch",t),void await c();if(r.instanceId!==t.instanceId||r.runtimeHandlerIdentity!==t.handlerIdentity||r.dispatchEpoch!==t.runtimeEpoch)return await this.reject(r,"authority_mismatch",t),void await c();const p=d.snapshot.request.payload;if(d.snapshot.operationId!==r.operationId||d.snapshot.requestMessageId!==r.requestMessageId||d.snapshot.requestHash!==r.requestHash||!semanticEqual(p,r))return void await this.handlePoison(e,"request_history_mismatch",t);const h=d.snapshot.started?.payload,m=void 0!==h&&h.operationId===r.operationId&&h.requestMessageId===r.requestMessageId&&h.requestHash===r.requestHash&&h.executionGrantHash===r.executionGrantHash&&h.handlerIdentity===t.handlerIdentity&&h.dispatchEpoch===t.runtimeEpoch&&h.method===r.method,I=await this.journal.get(r.operationId),E=await this.journal.acceptMessageRequest({operationId:r.operationId,command:r.command.name,paramsHash:r.paramsHash,runtimeEpoch:r.dispatchEpoch,requestMessageId:r.requestMessageId,requestHash:r.requestHash}),f=m?I:E,v=decideMessageServiceRecovery(d.snapshot,r,f,m);if("already_terminal"===v)return void await c();if(!CORE_RECOVERY_DECISIONS.has(v))return await this.markOutcomeUnknownIfMutable(E),await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN",`Types recovery policy requires ${v}; this runtime cannot safely replay Core`),(new Date).toISOString())}),void await c();if(await this.verifier.refresh(),!i)return m?await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN","retained started proof has no live execution grant"),(new Date).toISOString())}):await this.reject(r,"invalid_execution_grant",t),void await c();this.verifier.verifyAndDecodeGrant(i)||await this.verifier.refresh(!0);const _=()=>s.abort();let R;o.addEventListener("abort",_,{once:!0});try{R=await verifyAndCreateMessageServiceCoreExecutionAttempt({request:r,executionGrant:i,runtimeEpoch:t.runtimeEpoch,lease:t,verifier:this.verifier,allowExpiredGrant:m,abortSignal:s.signal,resolvePorts:async()=>await this.ports.resolve({request:r,executionGrant:i,command:r.command,getLease:()=>this.currentLease(t),journalStoreId:this.options.journalStoreId,journalGeneration:this.options.journalGeneration,journalRecord:E,abortSignal:s.signal})})}catch(e){if(o.removeEventListener("abort",_),s.abort(),!(e instanceof RuntimeResourceResolutionError))throw e;if("transient"===e.kind)return;return m?(await this.markOutcomeUnknownIfMutable(E),await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN","eager resources cannot be reconstructed after retained started proof"),(new Date).toISOString())})):await this.reject(r,"integrity_mismatch"===e.kind?"resource_integrity_mismatch":"resource_resolution_rejected",t),void await c()}if(!R.ok)return o.removeEventListener("abort",_),s.abort(),m?(await this.markOutcomeUnknownIfMutable(E),await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN","execution grant cannot be revalidated after retained started proof"),(new Date).toISOString())})):await this.reject(r,"invalid_execution_grant",t),void await c();if(!u||"prepared"!==E.phase&&"started"!==E.phase||s.abort(),s.signal.aborted&&!m)return o.removeEventListener("abort",_),await this.reject(r,"cancelled_before_dispatch",t),void await c();m?this.currentLease(t):await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),executionGrant:i,type:RUNTIME_MESSAGE_TYPES.started,payload:{...runtimeMessageBase(r),executionMode:"start",executionGrantHash:r.executionGrantHash,handlerIdentity:t.handlerIdentity,claimedAt:E.messageBinding?.acceptedAt??E.updatedAt}});const y=this.dispatcher.dispatch(r.command,R.context);y.completion.catch(()=>{}),n(()=>{s.abort(),y.cancel()}),s.signal.aborted&&y.cancel();const M=this.forwardProgress(r,y.progress,t).catch(()=>{});let w,g=!1;try{if(("mcp/set"===r.method||"mcp/unset"===r.method)&&hasEffectReceiptHandoff(y)){const e=await y.effectCommitted;if(e){if(e.operationId!==r.operationId||e.method!==r.command.name)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core MCP receipt correlation mismatch");const o=runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:r.method,result:e.result}),n={schemaVersion:1,operationId:r.operationId,method:r.method,coreResultRef:`sha256:${o}`,coreResultHash:o};w=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,n),g=!0,await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(r),effectRecord:n,effectRecordHash:w,committedAt:e.committedAt}}),await y.acknowledgeEffectReceipt({operationId:r.operationId,method:r.command.name,effectRecordHash:w})}}const o=await y.completion;await M;const n=await this.journal.get(r.operationId);if(!n||!isCommittedCorePhase(n.phase))throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core completed without a committed local journal record");const a=n.updatedAt;let i,s;if("query"!==RUNTIME_INVOCATION_RECOVERY_CLASSES[r.method]){const e=isAgentProjectionMethod(r.method),s=y.getSnapshot().projection,d=n.agentProjectionIntent??n.agentProjectionPreparationFailure;if(e&&!d)throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent effect committed without an atomic projection preparation record");if(!w){const e=d?.coreResultHash??runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:r.method,result:o}),i=n.agentProjectionIntent?.projectionRevision??(methodRequiresProjectionRevision(r.method)?s?.revision??`${Math.max(0,n.revision)}`:void 0),c={schemaVersion:1,operationId:r.operationId,method:r.method,coreResultRef:`sha256:${e}`,coreResultHash:e,...i?{projectionRevision:i}:{}};w=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,c),g=!0,await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(r),effectRecord:c,effectRecordHash:w,committedAt:a}})}let l=e?await awaitRuntimeProjection(y):s;if("session/set_model"===r.method&&"synced"!==l?.state&&(l=await this.projectSessionModelAfterEffect(r,R.context,n,l)),e&&n.agentProjectionIntent&&(!l||l.revision!==n.agentProjectionIntent.projectionRevision))throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent projection receipt revision mismatch");if("pending"===l?.state)return void await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionPending,payload:{...runtimeMessageBase(r),revision:l.revision}});if("synced"===l?.state&&await this.publishProjectionSynced(r,t,{...l,state:"synced"}),"blocked"===l?.state){await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionBlocked,payload:{...runtimeMessageBase(r),revision:l.revision,errorCode:"PROJECTION_BLOCKED"}});const e=RUNTIME_ERROR_DESCRIPTORS.PROJECTION_BLOCKED;return await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:{...runtimeMessageBase(r),outcome:"projection_blocked",response:{jsonrpc:"2.0",id:r.request.id,error:{code:e.jsonRpcCode,message:"PROJECTION_BLOCKED",data:{code:"PROJECTION_BLOCKED",operationId:r.operationId,effectState:"committed",retryMode:e.retryMode}}},effectRecordHash:w,completedAt:a}}),void await c()}i=e?l?.platformAgentId:void 0}try{s=sanitizeRuntimeServiceResult(r.method,o,R.context,i)}catch(e){throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed Core result cannot be mapped safely",void 0,{cause:e})}const d=runtimeMessageHash(RUNTIME_HASH_DOMAINS.serviceResult,{method:r.method,result:s});g=!0;const l=await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:{...runtimeMessageBase(r),outcome:"succeeded",response:{jsonrpc:"2.0",id:r.request.id,result:s},serviceResultHash:d,...w?{effectRecordHash:w}:{},completedAt:a}});if(this.observeRequestTerminalAccepted(e.deliveryId,l.message),("mcp/set"===r.method||"mcp/unset"===r.method)&&hasEffectReceiptHandoff(y)&&"function"==typeof y.acknowledgeTerminalReceipt){if(!w)throw new RuntimeCommandError("OUTCOME_UNKNOWN","MCP terminal response lacks its committed effect hash");await y.acknowledgeTerminalReceipt({operationId:r.operationId,method:r.command.name,effectRecordHash:w,terminalMessageId:l.message.message_id})}await c(),this.observeRequestAcknowledged(e.deliveryId,l.message)}catch(e){const o=await this.journal.get(r.operationId);if(o&&isCommittedCorePhase(o.phase)&&g)throw e;const n=!o||!isCommittedCorePhase(o.phase)||e instanceof RuntimeCommandError&&"OUTCOME_UNKNOWN"===e.code?e:new RuntimeCommandError("OUTCOME_UNKNOWN","Core effect committed before terminal mapping",void 0,{cause:e}),a=failurePayload(r,n,o?.updatedAt??(new Date).toISOString());await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:a}),await c()}finally{s.abort(),o.removeEventListener("abort",_)}}async handlePoison(e,t,o){this.currentLease(o);(await this.poisonJournal.record({delivery:e,reason:durablePoisonReason(t),limit:this.options.poisonDeliveryLimit??3})).terminal&&await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:this.currentLease(o)})}observeRequestTerminalAccepted(e,t){try{this.options.onRequestTerminalAccepted?.({deliveryId:e,message:t})}catch{}}observeRequestAcknowledged(e,t){try{this.options.onRequestAcknowledged?.({deliveryId:e,message:t})}catch{}}async forwardProgress(e,t,o){for await(const n of t)await this.channel.append({operationId:e.operationId,lease:this.currentLease(o),type:RUNTIME_MESSAGE_TYPES.progress,payload:{...runtimeMessageBase(e),phase:n.phase,progress:n}})}async reject(e,t,o){await this.channel.append({operationId:e.operationId,lease:this.currentLease(o),type:RUNTIME_MESSAGE_TYPES.final,payload:prestartFailurePayload(e,t)})}async markOutcomeUnknownIfMutable(e){"prepared"!==e.phase&&"started"!==e.phase||await this.journal.transition({operationId:e.operationId,expectedRevision:e.revision,phase:"outcome_unknown",errorCode:"OUTCOME_UNKNOWN"})}async projectSessionModelAfterEffect(e,t,o,n){const a=Object.freeze({state:"pending",revision:n?.revision??`${Math.max(0,o.revision)}`}),r=e.command;if("session.setModel"!==r.name||"conversation"!==r.context.scope)return a;const i=t.sessionModelProjection;if(!i)return a;try{await i.project({conversationId:r.context.conversation.runtimeConversationUid,modelOverrideId:r.params.modelOverrideId});const e=Object.freeze({state:"synced",revision:a.revision});try{const t=await this.journal.get(o.operationId)??o;if("projection_committed"===t.phase&&"synced"===t.projectionReceipt?.snapshot.state)return Object.freeze({state:"synced",revision:t.projectionReceipt.snapshot.revision});await this.journal.transition({operationId:t.operationId,expectedRevision:t.revision,phase:"projection_committed",projectionSnapshot:e})}catch{}return e}catch(e){return this.observeDeliveryError(e),a}}async publishProjectionSynced(e,t,o){const n=this.currentLease(t),a=await this.channel.history({operationId:e.operationId,lease:n});if("found"===a.status&&"synced"===a.snapshot.projectionState)return;const r={...runtimeMessageBase(e),revision:o.revision};("found"!==a.status||"reserved"===a.snapshot.projectionState||"pending"!==a.snapshot.projectionState&&void 0===a.snapshot.projectionPending)&&await this.channel.append({operationId:e.operationId,lease:n,type:RUNTIME_MESSAGE_TYPES.projectionPending,payload:r}),await this.channel.append({operationId:e.operationId,lease:n,type:RUNTIME_MESSAGE_TYPES.projectionSynced,payload:{...r,...o.platformAgentId?{platformAgentId:o.platformAgentId}:{}}})}currentLease(e){const t=this.getLease();if(!t||t.instanceId!==e.instanceId||t.leaseId!==e.leaseId||t.runtimeEpoch!==e.runtimeEpoch||t.handlerIdentity!==e.handlerIdentity||!Number.isFinite(Date.parse(t.leaseExpiresAt))||Date.parse(t.leaseExpiresAt)<=Date.now())throw new RuntimeCommandError("INSTANCE_EPOCH_MISMATCH","runtime message lease changed");return t}}function durablePoisonReason(e){const t=e.replace(/[^a-z0-9_:.-]+/gi,"_").replace(/^_+|_+$/g,"").slice(0,256);if(!t)throw new Error("poison reason is empty");return t}function isCommittedCorePhase(e){return"effect_committed"===e||"projection_committed"===e}const CORE_RECOVERY_DECISIONS=new Set(["execute","resume","replay_effect_committed","resume_projection","publish_committed_result","publish_projected_result","publish_terminal_result"]);function decideMessageServiceRecovery(e,t,o,n){const a=RUNTIME_INVOCATION_PARITY_ENTRIES.find(e=>e.acpMethod===t.method),r=e.terminal?"terminal":"blocked"===e.projectionState?"projection_blocked":"synced"===e.projectionState?"projection_synced":"pending"===e.projectionState?"projection_pending":e.effectCommitted?"runtime_committed":n?"running":"queued",i="failed"===o?.phase||"outcome_unknown"===o?.phase?"terminal":o?.phase,s=o&&o.runtimeEpoch===t.dispatchEpoch&&(void 0===o.messageBinding||o.messageBinding.requestMessageId===t.requestMessageId&&o.messageBinding.requestHash===t.requestHash)?"continuous":"discontinuous";return reduceRuntimeRecoveryPolicy({recoveryClass:a?.recoveryClass??RUNTIME_INVOCATION_RECOVERY_CLASSES[t.method],projectionRequired:"mcp/set"===t.method||"mcp/unset"===t.method||(a?.projectionRequired??!1),messageServicePhase:r,executionBoundaryCrossed:n,lineageContinuity:s,journalState:o?"available":"unavailable",...i?{journalPhase:i}:{}})}export function sanitizeRuntimeServiceResult(e,t,o,n){if("session/clear"===e)return serviceClearResult(t,o?.historyBoundary);if("session/set_model"===e)return serviceSetModelResult(t,o);if(!(e in SERVICE_CANONICAL_COMMAND_NAMES))return t;const a="cron/status"===e&&record(t)?{enabled:t.enabled,jobCount:t.jobCount,..."number"==typeof t.nextWakeAtMs?{nextWakeAtMs:t.nextWakeAtMs}:{}}:t;return mapCanonicalResultToService(e,a,{...n?{platformAgentId:n}:{},classifySkillOrigin:e=>e.bundled?{origin:"bundled",slug:e.slug}:{origin:"legacy_runtime",slug:e.slug},resolveMcpView:e=>({name:e.name,transport:e.transport,origin:"legacy_runtime",...void 0===e.connected?{}:{connected:e.connected},...void 0===e.connectionTimeoutMs?{}:{connectionTimeoutMs:e.connectionTimeoutMs}}),sanitizeDiagnostic:e=>/^[A-Z][A-Z0-9_]{0,63}$/.test(e)?e:"RUNTIME_EXECUTION_FAILED"})}function serviceClearResult(e,t){if(!record(e)||!0!==e.cleared||"string"!=typeof e.historyGeneration||!t||"message-service"!==t.kind)throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed clear result lacks its Message Service receipt");const o=t;if(!o.committedIntentId)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Message Service clear receipt is unavailable");return{...e,clearIntentId:o.committedIntentId()}}function serviceSetModelResult(e,t){if(!record(e)||"string"!=typeof e.effectiveModelId||!t)throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed set_model result is invalid");const o=t.verifiedMessageGrant?.claims.command;if(!o||"session.setModel"!==o.name||"conversation"!==o.context.scope)throw new RuntimeCommandError("OUTCOME_UNKNOWN","set_model Service target receipt is unavailable");const n=o.params.modelOverrideId;return{conversationId:o.context.conversation.runtimeConversationUid,...null===n?{defaultModelId:e.effectiveModelId}:{},modelOverrideId:n,effectiveModelId:e.effectiveModelId,effectState:"committed",projectionState:"synced",operationId:t.operationId}}function isAgentProjectionMethod(e){return"agent/create"===e||"agent/update"===e||"agent/delete"===e||"agent/applyTemplate"===e}function methodRequiresProjectionRevision(e){return isAgentProjectionMethod(e)||"session/set_model"===e}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function sameLeaseBinding(e,t){return e.instanceId===t.instanceId&&e.leaseId===t.leaseId&&e.handlerIdentity===t.handlerIdentity&&e.runtimeEpoch===t.runtimeEpoch}function failurePayload(e,t,o){const n=t instanceof RuntimeCommandError?t.code:"RUNTIME_EXECUTION_FAILED",a=n in RUNTIME_ERROR_DESCRIPTORS?n:"RUNTIME_EXECUTION_FAILED",r="OUTCOME_UNKNOWN"===a||"INSTANCE_EPOCH_MISMATCH"===a?"outcome_unknown":"COMMAND_EXPIRED"===a?"expired":"OPERATION_CANCELLED"===a?"cancelled":"failed",i="outcome_unknown"===r?"OUTCOME_UNKNOWN":a,s=RUNTIME_ERROR_DESCRIPTORS[i],c="outcome_unknown"===r?"outcome_unknown":r;return{...runtimeMessageBase(e),outcome:r,response:{jsonrpc:"2.0",id:e.request.id,error:{code:s.jsonRpcCode,message:i,data:{code:i,operationId:e.operationId,effectState:c,retryMode:s.retryMode}}},completedAt:o}}function prestartFailurePayload(e,t){const o="invalid_execution_grant"===t?"INVALID_EXECUTION_GRANT":"unsupported_method"===t?"METHOD_NOT_SUPPORTED":"authority_mismatch"===t?"INSTANCE_EPOCH_MISMATCH":"execution_deadline_exceeded"===t?"COMMAND_EXPIRED":"cancelled_before_dispatch"===t?"OPERATION_CANCELLED":"resource_resolution_rejected"===t?"INVALID_INVOCATION":"RUNTIME_EXECUTION_FAILED",n=RUNTIME_ERROR_DESCRIPTORS[o in RUNTIME_ERROR_DESCRIPTORS?o:"RUNTIME_EXECUTION_FAILED"],a="COMMAND_EXPIRED"===o?"expired":"OPERATION_CANCELLED"===o?"cancelled":"failed";return{...runtimeMessageBase(e),outcome:a,response:{jsonrpc:"2.0",id:e.request.id,error:{code:n.jsonRpcCode,message:o,data:{code:o,operationId:e.operationId,effectState:a,retryMode:n.retryMode}}},completedAt:(new Date).toISOString()}}function abortable(e,t){return t?t.aborted?Promise.resolve():new Promise((o,n)=>{const a=()=>o();t.addEventListener("abort",a,{once:!0}),e.then(()=>{t.removeEventListener("abort",a),o()},e=>{t.removeEventListener("abort",a),n(e)})}):e}function delay(e,t){return new Promise(o=>{if(t?.aborted)return o();const n=()=>{clearTimeout(a),o()},a=setTimeout(()=>{t?.removeEventListener("abort",n),o()},e);t?.addEventListener("abort",n,{once:!0})})}
1
+ import{RUNTIME_ERROR_DESCRIPTORS,RUNTIME_HASH_DOMAINS,RUNTIME_INVOCATION_PARITY_ENTRIES,RUNTIME_INVOCATION_RECOVERY_CLASSES,SERVICE_CANONICAL_COMMAND_NAMES,mapCanonicalResultToService}from"@beeos-ai/beeos-types/runtime";import{reduceRuntimeRecoveryPolicy}from"@beeos-ai/beeos-types/runtime/internal";import{verifyAndCreateMessageServiceCoreExecutionAttempt}from"./core-execution-attempt-context.js";import{awaitRuntimeProjection,hasEffectReceiptHandoff,RuntimeCommandError}from"./runtime-command-dispatcher.js";import{createRuntimeAppendTimingRecorder}from"./runtime-append-timing.js";import{RUNTIME_MESSAGE_TYPES,runtimeMessageBase,runtimeMessageHash,semanticEqual,validateRuntimeCancelEnvelope,validateRuntimeRequestEnvelope}from"./runtime-message-channel-contract.js";import{RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{RuntimeTransportFailurePolicy}from"./runtime-transport-failure-policy.js";export class MsRuntimeMessageChannelAdapter{dispatcher;channel;verifier;ports;journal;getLease;options;controller;running;workers=new Map;poisonJournal;renewTransportFailurePolicy=new RuntimeTransportFailurePolicy;receiptHistory=this;constructor(e,t,o,n,a,r,i){if(this.dispatcher=e,this.channel=t,this.verifier=o,this.ports=n,this.journal=a,this.getLease=r,this.options=i,!i.poisonJournal)throw new Error("durable poison journal is required");if(this.poisonJournal=i.poisonJournal,!Number.isSafeInteger(i.poisonDeliveryLimit??3)||(i.poisonDeliveryLimit??3)<=0)throw new Error("poison delivery limit must be a positive integer")}start(){if(this.running)return;const e=new AbortController;this.controller=e;const t=this.loop(e.signal).catch(t=>{e.signal.aborted||this.observeDeliveryError(t)}).finally(()=>{this.running===t&&(this.running=void 0)});this.running=t}async stop(e){this.controller?.abort();for(const e of this.workers.values())e.cancel?.();this.running&&await abortable(this.running,e)}async readOperationHistory(e){const t=this.getLease();if(!t)return{status:"unavailable"};try{return await this.channel.history({operationId:e.operationId,lease:t})}catch{return{status:"unavailable"}}}async loop(e){let t;(()=>{t||(t=setInterval(()=>{this.renewActive().catch(e=>this.observeDeliveryError(e))},this.options.renewIntervalMs??2e4),t.unref?.())})();try{for(;!e.aborted;){const t=this.getLease();if(!t){await delay(this.options.idleDelayMs??500,e);continue}let o;try{o=await this.channel.read({maxCount:this.options.readCount??32,blockMs:this.options.blockMs??5e3,lease:t,signal:e})}catch(t){if(e.aborted)return;this.observeDeliveryError(t),await delay(this.options.idleDelayMs??500,e);continue}for(const n of o){if(e.aborted)break;if(n.message.type===RUNTIME_MESSAGE_TYPES.cancel){await this.handleCancel(n,t).catch(e=>this.observeDeliveryError(e));continue}const o=n.message.channel_id;if(this.workers.has(o))continue;const a={deliveryId:n.deliveryId,claimedLease:t,completion:Promise.resolve()};a.completion=this.handleRequest(n,t,e,e=>{a.cancel=e}).catch(e=>this.observeDeliveryError(e)).finally(()=>{this.workers.delete(o)}),this.workers.set(o,a)}}}finally{t&&clearInterval(t),await Promise.allSettled([...this.workers.values()].map(e=>e.completion))}}observeDeliveryError(e){try{this.options.onDeliveryError?.(e)}catch{}}observeRequestFailure(e,t,o){try{this.options.onRequestFailure?.({operationId:e,method:t,error:o})}catch{}}async renewActive(){const e=this.getLease();if(!e)return;const t=[];for(const o of this.workers.values())sameLeaseBinding(o.claimedLease,e)?t.push(o.deliveryId):o.cancel?.();if(!t.length)return;let o;try{o=await this.channel.renew({deliveryIds:t,lease:e})}catch(e){if(this.observeDeliveryError(e),this.renewTransportFailurePolicy.recordFailure()){const e=new Set(t);for(const t of this.workers.values())e.has(t.deliveryId)&&t.cancel?.()}return}if(this.renewTransportFailurePolicy.recordSuccess(),o.notPending.length){const e=new Set(o.notPending);for(const t of this.workers.values())e.has(t.deliveryId)&&t.cancel?.()}}async handleCancel(e,t){const o=validateRuntimeCancelEnvelope(e.message);if(!o.ok)return void await this.handlePoison(e,`invalid_cancel:${o.reason}`,t);const n=o.envelope.payload,a=await this.channel.history({operationId:n.operationId,lease:t});if("found"===a.status&&a.snapshot.terminal&&"final_message"===a.snapshot.terminalSource)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});if("not_found"===a.status||"expired"===a.status)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});const r=a.snapshot.request.payload;r.operationId===n.operationId&&r.requestMessageId===n.requestMessageId&&r.requestHash===n.requestHash&&r.dispatchEpoch===n.dispatchEpoch&&r.method===n.method?(await this.journal.observeCancellation({operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,eventId:n.eventId}),this.workers.get(n.operationId)?.cancel?.(),await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t})):await this.handlePoison(e,"cancel_history_mismatch",t)}async handleRequest(e,t,o,n){const a=validateRuntimeRequestEnvelope(e.message);if(!a.ok)return void await this.handlePoison(e,`invalid_request:${a.reason}`,t);const r=a.envelope.payload,i=e.executionGrant,s=createRuntimeAppendTimingRecorder("runtime-op",{operationId:r.operationId,method:r.method}),c=new AbortController;n(()=>c.abort());const d=async()=>{await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:this.currentLease(t)}),s.mark("ack")},l=await this.channel.history({operationId:r.operationId,lease:t});if(s.mark("history"),"not_found"===l.status||"expired"===l.status)return void await d();if(l.snapshot.terminal&&"final_message"===l.snapshot.terminalSource)return void await d();if(this.options.allowedMethods&&!this.options.allowedMethods.has(r.method))return await this.reject(r,"unsupported_method",t),void await d();if("deadline"===l.snapshot.terminalSource||Date.parse(r.executionDeadlineAt)<=Date.now()||!l.snapshot.started&&Date.parse(r.claimDeadlineAt)<=Date.now())return await this.reject(r,"execution_deadline_exceeded",t),void await d();const p=await this.journal.getCancellation(r.operationId);s.mark("journal-cancel-read");const u=l.snapshot.cancelRequested||null!==p;if(u&&"queued"===l.snapshot.status)return await this.reject(r,"cancelled_before_dispatch",t),void await d();if(r.instanceId!==t.instanceId||r.runtimeHandlerIdentity!==t.handlerIdentity||r.dispatchEpoch!==t.runtimeEpoch)return await this.reject(r,"authority_mismatch",t),void await d();const m=l.snapshot.request.payload;if(l.snapshot.operationId!==r.operationId||l.snapshot.requestMessageId!==r.requestMessageId||l.snapshot.requestHash!==r.requestHash||!semanticEqual(m,r))return void await this.handlePoison(e,"request_history_mismatch",t);const h=l.snapshot.started?.payload,I=void 0!==h&&h.operationId===r.operationId&&h.requestMessageId===r.requestMessageId&&h.requestHash===r.requestHash&&h.executionGrantHash===r.executionGrantHash&&h.handlerIdentity===t.handlerIdentity&&h.dispatchEpoch===t.runtimeEpoch&&h.method===r.method,E=await this.journal.get(r.operationId);s.mark("journal-prior");const f=await this.journal.acceptMessageRequest({operationId:r.operationId,command:r.command.name,paramsHash:r.paramsHash,runtimeEpoch:r.dispatchEpoch,requestMessageId:r.requestMessageId,requestHash:r.requestHash});s.mark("journal-accept");const v=I?E:f,R=decideMessageServiceRecovery(l.snapshot,r,v,I);if("already_terminal"===R)return void await d();if(!CORE_RECOVERY_DECISIONS.has(R))return await this.markOutcomeUnknownIfMutable(f),await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN",`Types recovery policy requires ${R}; this runtime cannot safely replay Core`),(new Date).toISOString())}),void await d();if(await this.verifier.refresh(),s.mark("verifier-refresh"),!i)return I?await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN","retained started proof has no live execution grant"),(new Date).toISOString())}):await this.reject(r,"invalid_execution_grant",t),void await d();this.verifier.verifyAndDecodeGrant(i)||await this.verifier.refresh(!0);const _=()=>c.abort();let y;o.addEventListener("abort",_,{once:!0});try{y=await verifyAndCreateMessageServiceCoreExecutionAttempt({request:r,executionGrant:i,runtimeEpoch:t.runtimeEpoch,lease:t,verifier:this.verifier,allowExpiredGrant:I,abortSignal:c.signal,resolvePorts:async()=>await this.ports.resolve({request:r,executionGrant:i,command:r.command,getLease:()=>this.currentLease(t),journalStoreId:this.options.journalStoreId,journalGeneration:this.options.journalGeneration,journalRecord:f,abortSignal:c.signal})})}catch(e){if(o.removeEventListener("abort",_),c.abort(),!(e instanceof RuntimeResourceResolutionError))throw e;if("transient"===e.kind)return;return I?(await this.markOutcomeUnknownIfMutable(f),await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN","eager resources cannot be reconstructed after retained started proof"),(new Date).toISOString())})):await this.reject(r,"integrity_mismatch"===e.kind?"resource_integrity_mismatch":"resource_resolution_rejected",t),void await d()}if(!y.ok)return o.removeEventListener("abort",_),c.abort(),I?(await this.markOutcomeUnknownIfMutable(f),await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN","execution grant cannot be revalidated after retained started proof"),(new Date).toISOString())})):await this.reject(r,"invalid_execution_grant",t),void await d();if(!u||"prepared"!==f.phase&&"started"!==f.phase||c.abort(),c.signal.aborted&&!I)return o.removeEventListener("abort",_),await this.reject(r,"cancelled_before_dispatch",t),void await d();s.mark("attempt"),I?this.currentLease(t):(await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),executionGrant:i,type:RUNTIME_MESSAGE_TYPES.started,payload:{...runtimeMessageBase(r),executionMode:"start",executionGrantHash:r.executionGrantHash,handlerIdentity:t.handlerIdentity,claimedAt:f.messageBinding?.acceptedAt??f.updatedAt}}),s.mark("append-started"));const M=this.dispatcher.dispatch(r.command,y.context);M.completion.catch(()=>{}),n(()=>{c.abort(),M.cancel()}),c.signal.aborted&&M.cancel();const w=this.forwardProgress(r,M.progress,t).catch(()=>{});let g,S=!1;try{if(("mcp/set"===r.method||"mcp/unset"===r.method)&&hasEffectReceiptHandoff(M)){const e=await M.effectCommitted;if(e){if(e.operationId!==r.operationId||e.method!==r.command.name)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core MCP receipt correlation mismatch");const o=runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:r.method,result:e.result}),n={schemaVersion:1,operationId:r.operationId,method:r.method,coreResultRef:`sha256:${o}`,coreResultHash:o};g=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,n),S=!0,await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(r),effectRecord:n,effectRecordHash:g,committedAt:e.committedAt}}),await M.acknowledgeEffectReceipt({operationId:r.operationId,method:r.command.name,effectRecordHash:g})}}const o=await M.completion;await w,s.mark("core");const n=await this.journal.get(r.operationId);if(s.mark("journal-after-core"),!n||!isCommittedCorePhase(n.phase))throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core completed without a committed local journal record");const a=n.updatedAt;let i,c;if("query"!==RUNTIME_INVOCATION_RECOVERY_CLASSES[r.method]){const e=isAgentProjectionMethod(r.method),c=M.getSnapshot().projection,l=n.agentProjectionIntent??n.agentProjectionPreparationFailure;if(e&&!l)throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent effect committed without an atomic projection preparation record");if(!g){const e=l?.coreResultHash??runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:r.method,result:o}),i=n.agentProjectionIntent?.projectionRevision??(methodRequiresProjectionRevision(r.method)?c?.revision??`${Math.max(0,n.revision)}`:void 0),d={schemaVersion:1,operationId:r.operationId,method:r.method,coreResultRef:`sha256:${e}`,coreResultHash:e,...i?{projectionRevision:i}:{}};g=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,d),S=!0,await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(r),effectRecord:d,effectRecordHash:g,committedAt:a}}),s.mark("append-effect")}let p=e?await awaitRuntimeProjection(M):c;if("session/set_model"===r.method&&"synced"!==p?.state&&(p=await this.projectSessionModelAfterEffect(r,y.context,n,p),s.mark("set-model-projection")),e&&n.agentProjectionIntent&&(!p||p.revision!==n.agentProjectionIntent.projectionRevision))throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent projection receipt revision mismatch");if("pending"===p?.state)return void await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionPending,payload:{...runtimeMessageBase(r),revision:p.revision}});if("synced"===p?.state&&await this.publishProjectionSynced(r,t,{...p,state:"synced"}),"blocked"===p?.state){await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionBlocked,payload:{...runtimeMessageBase(r),revision:p.revision,errorCode:"PROJECTION_BLOCKED"}});const e=RUNTIME_ERROR_DESCRIPTORS.PROJECTION_BLOCKED;return await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:{...runtimeMessageBase(r),outcome:"projection_blocked",response:{jsonrpc:"2.0",id:r.request.id,error:{code:e.jsonRpcCode,message:"PROJECTION_BLOCKED",data:{code:"PROJECTION_BLOCKED",operationId:r.operationId,effectState:"committed",retryMode:e.retryMode}}},effectRecordHash:g,completedAt:a}}),void await d()}i=e?p?.platformAgentId:void 0}try{c=sanitizeRuntimeServiceResult(r.method,o,y.context,i)}catch(e){throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed Core result cannot be mapped safely",void 0,{cause:e})}const l=runtimeMessageHash(RUNTIME_HASH_DOMAINS.serviceResult,{method:r.method,result:c});S=!0;const p=await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:{...runtimeMessageBase(r),outcome:"succeeded",response:{jsonrpc:"2.0",id:r.request.id,result:c},serviceResultHash:l,...g?{effectRecordHash:g}:{},completedAt:a}});if(s.mark("append-final"),this.observeRequestTerminalAccepted(e.deliveryId,p.message),("mcp/set"===r.method||"mcp/unset"===r.method)&&hasEffectReceiptHandoff(M)&&"function"==typeof M.acknowledgeTerminalReceipt){if(!g)throw new RuntimeCommandError("OUTCOME_UNKNOWN","MCP terminal response lacks its committed effect hash");await M.acknowledgeTerminalReceipt({operationId:r.operationId,method:r.command.name,effectRecordHash:g,terminalMessageId:p.message.message_id})}await d(),this.observeRequestAcknowledged(e.deliveryId,p.message)}catch(e){const o=await this.journal.get(r.operationId);if(o&&isCommittedCorePhase(o.phase)&&S)throw e;const n=!o||!isCommittedCorePhase(o.phase)||e instanceof RuntimeCommandError&&"OUTCOME_UNKNOWN"===e.code?e:new RuntimeCommandError("OUTCOME_UNKNOWN","Core effect committed before terminal mapping",void 0,{cause:e});this.observeRequestFailure(r.operationId,r.method,n);const a=failurePayload(r,n,o?.updatedAt??(new Date).toISOString());await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:a}),s.mark("append-terminal-failure"),await d()}finally{s.finish(),c.abort(),o.removeEventListener("abort",_)}}async handlePoison(e,t,o){this.currentLease(o);(await this.poisonJournal.record({delivery:e,reason:durablePoisonReason(t),limit:this.options.poisonDeliveryLimit??3})).terminal&&await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:this.currentLease(o)})}observeRequestTerminalAccepted(e,t){try{this.options.onRequestTerminalAccepted?.({deliveryId:e,message:t})}catch{}}observeRequestAcknowledged(e,t){try{this.options.onRequestAcknowledged?.({deliveryId:e,message:t})}catch{}}async forwardProgress(e,t,o){for await(const n of t)await this.channel.append({operationId:e.operationId,lease:this.currentLease(o),type:RUNTIME_MESSAGE_TYPES.progress,payload:{...runtimeMessageBase(e),phase:n.phase,progress:n}})}async reject(e,t,o){await this.channel.append({operationId:e.operationId,lease:this.currentLease(o),type:RUNTIME_MESSAGE_TYPES.final,payload:prestartFailurePayload(e,t)})}async markOutcomeUnknownIfMutable(e){"prepared"!==e.phase&&"started"!==e.phase||await this.journal.transition({operationId:e.operationId,expectedRevision:e.revision,phase:"outcome_unknown",errorCode:"OUTCOME_UNKNOWN"})}async projectSessionModelAfterEffect(e,t,o,n){const a=Object.freeze({state:"pending",revision:n?.revision??`${Math.max(0,o.revision)}`}),r=e.command;if("session.setModel"!==r.name||"conversation"!==r.context.scope)return a;const i=t.sessionModelProjection;if(!i)return a;try{await i.project({conversationId:r.context.conversation.runtimeConversationUid,modelOverrideId:r.params.modelOverrideId});const e=Object.freeze({state:"synced",revision:a.revision});try{const t=await this.journal.get(o.operationId)??o;if("projection_committed"===t.phase&&"synced"===t.projectionReceipt?.snapshot.state)return Object.freeze({state:"synced",revision:t.projectionReceipt.snapshot.revision});await this.journal.transition({operationId:t.operationId,expectedRevision:t.revision,phase:"projection_committed",projectionSnapshot:e})}catch{}return e}catch(e){return this.observeDeliveryError(e),a}}async publishProjectionSynced(e,t,o){const n=this.currentLease(t),a=await this.channel.history({operationId:e.operationId,lease:n});if("found"===a.status&&"synced"===a.snapshot.projectionState)return;const r={...runtimeMessageBase(e),revision:o.revision};("found"!==a.status||"reserved"===a.snapshot.projectionState||"pending"!==a.snapshot.projectionState&&void 0===a.snapshot.projectionPending)&&await this.channel.append({operationId:e.operationId,lease:n,type:RUNTIME_MESSAGE_TYPES.projectionPending,payload:r}),await this.channel.append({operationId:e.operationId,lease:n,type:RUNTIME_MESSAGE_TYPES.projectionSynced,payload:{...r,...o.platformAgentId?{platformAgentId:o.platformAgentId}:{}}})}currentLease(e){const t=this.getLease();if(!t||t.instanceId!==e.instanceId||t.leaseId!==e.leaseId||t.runtimeEpoch!==e.runtimeEpoch||t.handlerIdentity!==e.handlerIdentity||!Number.isFinite(Date.parse(t.leaseExpiresAt))||Date.parse(t.leaseExpiresAt)<=Date.now())throw new RuntimeCommandError("INSTANCE_EPOCH_MISMATCH","runtime message lease changed");return t}}function durablePoisonReason(e){const t=e.replace(/[^a-z0-9_:.-]+/gi,"_").replace(/^_+|_+$/g,"").slice(0,256);if(!t)throw new Error("poison reason is empty");return t}function isCommittedCorePhase(e){return"effect_committed"===e||"projection_committed"===e}const CORE_RECOVERY_DECISIONS=new Set(["execute","resume","replay_effect_committed","resume_projection","publish_committed_result","publish_projected_result","publish_terminal_result"]);function decideMessageServiceRecovery(e,t,o,n){const a=RUNTIME_INVOCATION_PARITY_ENTRIES.find(e=>e.acpMethod===t.method),r=e.terminal?"terminal":"blocked"===e.projectionState?"projection_blocked":"synced"===e.projectionState?"projection_synced":"pending"===e.projectionState?"projection_pending":e.effectCommitted?"runtime_committed":n?"running":"queued",i="failed"===o?.phase||"outcome_unknown"===o?.phase?"terminal":o?.phase,s=o&&o.runtimeEpoch===t.dispatchEpoch&&(void 0===o.messageBinding||o.messageBinding.requestMessageId===t.requestMessageId&&o.messageBinding.requestHash===t.requestHash)?"continuous":"discontinuous";return reduceRuntimeRecoveryPolicy({recoveryClass:a?.recoveryClass??RUNTIME_INVOCATION_RECOVERY_CLASSES[t.method],projectionRequired:"mcp/set"===t.method||"mcp/unset"===t.method||(a?.projectionRequired??!1),messageServicePhase:r,executionBoundaryCrossed:n,lineageContinuity:s,journalState:o?"available":"unavailable",...i?{journalPhase:i}:{}})}export function sanitizeRuntimeServiceResult(e,t,o,n){if("session/clear"===e)return serviceClearResult(t,o?.historyBoundary);if("session/set_model"===e)return serviceSetModelResult(t,o);if(!(e in SERVICE_CANONICAL_COMMAND_NAMES))return t;const a="cron/status"===e&&record(t)?{enabled:t.enabled,jobCount:t.jobCount,..."number"==typeof t.nextWakeAtMs?{nextWakeAtMs:t.nextWakeAtMs}:{}}:t;return mapCanonicalResultToService(e,a,{...n?{platformAgentId:n}:{},classifySkillOrigin:e=>e.bundled?{origin:"bundled",slug:e.slug}:{origin:"legacy_runtime",slug:e.slug},resolveMcpView:e=>({name:e.name,transport:e.transport,origin:"legacy_runtime",...void 0===e.connected?{}:{connected:e.connected},...void 0===e.connectionTimeoutMs?{}:{connectionTimeoutMs:e.connectionTimeoutMs}}),sanitizeDiagnostic:e=>/^[A-Z][A-Z0-9_]{0,63}$/.test(e)?e:"RUNTIME_EXECUTION_FAILED"})}function serviceClearResult(e,t){if(!record(e)||!0!==e.cleared||"string"!=typeof e.historyGeneration||!t||"message-service"!==t.kind)throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed clear result lacks its Message Service receipt");const o=t;if(!o.committedIntentId)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Message Service clear receipt is unavailable");return{...e,clearIntentId:o.committedIntentId()}}function serviceSetModelResult(e,t){if(!record(e)||"string"!=typeof e.effectiveModelId||!t)throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed set_model result is invalid");const o=t.verifiedMessageGrant?.claims.command;if(!o||"session.setModel"!==o.name||"conversation"!==o.context.scope)throw new RuntimeCommandError("OUTCOME_UNKNOWN","set_model Service target receipt is unavailable");const n=o.params.modelOverrideId;return{conversationId:o.context.conversation.runtimeConversationUid,...null===n?{defaultModelId:e.effectiveModelId}:{},modelOverrideId:n,effectiveModelId:e.effectiveModelId,effectState:"committed",projectionState:"synced",operationId:t.operationId}}function isAgentProjectionMethod(e){return"agent/create"===e||"agent/update"===e||"agent/delete"===e||"agent/applyTemplate"===e}function methodRequiresProjectionRevision(e){return isAgentProjectionMethod(e)||"session/set_model"===e}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function sameLeaseBinding(e,t){return e.instanceId===t.instanceId&&e.leaseId===t.leaseId&&e.handlerIdentity===t.handlerIdentity&&e.runtimeEpoch===t.runtimeEpoch}function failurePayload(e,t,o){const n=t instanceof RuntimeCommandError?t.code:"RUNTIME_EXECUTION_FAILED",a=n in RUNTIME_ERROR_DESCRIPTORS?n:"RUNTIME_EXECUTION_FAILED",r="OUTCOME_UNKNOWN"===a||"INSTANCE_EPOCH_MISMATCH"===a?"outcome_unknown":"COMMAND_EXPIRED"===a?"expired":"OPERATION_CANCELLED"===a?"cancelled":"failed",i="outcome_unknown"===r?"OUTCOME_UNKNOWN":a,s=RUNTIME_ERROR_DESCRIPTORS[i],c="outcome_unknown"===r?"outcome_unknown":r;return{...runtimeMessageBase(e),outcome:r,response:{jsonrpc:"2.0",id:e.request.id,error:{code:s.jsonRpcCode,message:i,data:{code:i,operationId:e.operationId,effectState:c,retryMode:s.retryMode}}},completedAt:o}}function prestartFailurePayload(e,t){const o="invalid_execution_grant"===t?"INVALID_EXECUTION_GRANT":"unsupported_method"===t?"METHOD_NOT_SUPPORTED":"authority_mismatch"===t?"INSTANCE_EPOCH_MISMATCH":"execution_deadline_exceeded"===t?"COMMAND_EXPIRED":"cancelled_before_dispatch"===t?"OPERATION_CANCELLED":"resource_resolution_rejected"===t?"INVALID_INVOCATION":"RUNTIME_EXECUTION_FAILED",n=RUNTIME_ERROR_DESCRIPTORS[o in RUNTIME_ERROR_DESCRIPTORS?o:"RUNTIME_EXECUTION_FAILED"],a="COMMAND_EXPIRED"===o?"expired":"OPERATION_CANCELLED"===o?"cancelled":"failed";return{...runtimeMessageBase(e),outcome:a,response:{jsonrpc:"2.0",id:e.request.id,error:{code:n.jsonRpcCode,message:o,data:{code:o,operationId:e.operationId,effectState:a,retryMode:n.retryMode}}},completedAt:(new Date).toISOString()}}function abortable(e,t){return t?t.aborted?Promise.resolve():new Promise((o,n)=>{const a=()=>o();t.addEventListener("abort",a,{once:!0}),e.then(()=>{t.removeEventListener("abort",a),o()},e=>{t.removeEventListener("abort",a),n(e)})}):e}function delay(e,t){return new Promise(o=>{if(t?.aborted)return o();const n=()=>{clearTimeout(a),o()},a=setTimeout(()=>{t?.removeEventListener("abort",n),o()},e);t?.addEventListener("abort",n,{once:!0})})}
@@ -0,0 +1 @@
1
+ import{performance}from"node:perf_hooks";const DEFAULT_SLOW_TOTAL_MS=1e3;export function runtimeAppendTimingEnabled(e=process.env){const n=e.RUNTIME_APPEND_TIMING?.trim().toLowerCase();return void 0!==n&&""!==n&&"0"!==n&&"false"!==n&&"off"!==n}function slowTotalMs(e=process.env){const n=Number(e.RUNTIME_APPEND_TIMING_SLOW_MS);return Number.isFinite(n)&&n>0?n:1e3}export function createRuntimeAppendTimingRecorder(e,n={},o=e=>{}){const r=performance.now();let t=r,s=!1;const i=[];return{mark(e){const n=performance.now();i.push(`${e}=${(n-t).toFixed(1)}ms`),t=n},async span(e,n){const o=performance.now();try{return await n()}finally{const n=performance.now();i.push(`${e}=${(n-o).toFixed(1)}ms`),t=n}},finish(t){if(s)return;s=!0;const c=performance.now()-r;if(!runtimeAppendTimingEnabled()&&c<slowTotalMs())return;const m=Object.entries(n).map(([e,n])=>`${e}=${n}`).join(" ");o(`[RUNTIME_APPEND_TIMING] scope=${e}${m?` ${m}`:""}${t?` outcome=${t}`:""} ${i.join(" ")} total=${c.toFixed(1)}ms`)}}}
@@ -1 +1 @@
1
- import{createHash,randomUUID}from"node:crypto";import{readFile}from"node:fs/promises";import{canonicalizeJcs}from"@beeos-ai/beeos-types/runtime";import{withOwnedDirectoryLock}from"./owned-directory-lock.js";import{MonotonicJournalSegments}from"./monotonic-journal-segments.js";const HASH_ONLY_RESULT_COMMANDS=new Set(["session.load","session.history","session.list","agent.listProjection","skills.list","models.list","cron.list","cron.status","cron.runs","mcp.list","terminal.ping","terminal.wait"]),SAFE_CANONICAL_RESULT_COMMANDS=new Set(["session.clear","session.setMode","session.setModel","session.cancel","skills.install","skills.uninstall","skills.setEnabled","cron.remove","mcp.set","mcp.unset","terminal.input","terminal.resize","terminal.close","terminal.release","terminal.kill","canvas.toggle","canvas.clear","canvas.reference","canvas.dimensions"]);export const runtimeCommandResultPersistencePolicy=e=>SAFE_CANONICAL_RESULT_COMMANDS.has(e)?"canonical_result":"hash_only";export const runtimeCommandIsReplayableQuery=e=>HASH_ONLY_RESULT_COMMANDS.has(e);export const runtimeCommandResultHash=e=>createHash("sha256").update(canonicalizeJcs(e)).digest("hex");const emptyDocument=()=>({version:2,lineage:{journalStoreId:randomUUID(),journalGeneration:"1"},records:[],cancellations:[]});export class RuntimeCommandJournalConflictError extends Error{constructor(e){super(e),this.name="RuntimeCommandJournalConflictError"}}export class FileRuntimeCommandJournal{file;retentionMs;now;publication;segments;constructor(e,t=6048e5,n=Date.now,o={}){this.file=e,this.retentionMs=t,this.now=n,this.publication=o,this.segments=new MonotonicJournalSegments(e)}async lineage(){return await this.update(e=>({...e.lineage}))}async advanceLineageGeneration(e){return await this.update(t=>{if(t.lineage.journalStoreId!==e.journalStoreId)throw new RuntimeCommandJournalConflictError("journal store changed during incarnation advance");const n=BigInt(t.lineage.journalGeneration),o=BigInt(e.floor),r=(n>o?n:o)+1n;if(r>9223372036854775807n)throw new RuntimeCommandJournalConflictError("journal generation overflow");return t.lineage={journalStoreId:t.lineage.journalStoreId,journalGeneration:String(r)},{...t.lineage}})}async get(e){return(await this.read()).records.find(t=>t.operationId===e)??null}async getCancellation(e){return(await this.read()).cancellations.find(t=>t.operationId===e)??null}async listMcpLiveReleases(){const e=await this.read();return Object.freeze(e.records.flatMap(e=>e.mcpLiveRelease?[e.mcpLiveRelease]:[]))}async listMcpActiveDesiredStates(){const e=await this.read();return Object.freeze(e.records.flatMap(e=>e.mcpActiveDesiredState?[e.mcpActiveDesiredState]:[]))}async recordMcpDesiredStateCommit(e){const t=normalizeMcpDesiredStateCommit(e);return await this.update(e=>{const n=e.records.findIndex(e=>e.operationId===t.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP desired state has no command record");const o=e.records[n];if(assertMcpCommitBindsRecord(t,o),"started"!==o.phase){if(sameMcpDesiredStateCommit(o.mcpDesiredStateCommit,t))return o;throw new RuntimeCommandJournalConflictError("MCP desired state commit crossed its journal boundary")}if(o.mcpDesiredStateCommit){if(sameMcpDesiredStateCommit(o.mcpDesiredStateCommit,t))return o;throw new RuntimeCommandJournalConflictError("MCP desired state commit conflicts with journal")}const r={...o,mcpDesiredStateCommit:t,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return e.records[n]=r,r})}async releaseMcpLiveEffect(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP release has no command record");const o=t.records[n];if("effect_committed"!==o.phase&&"projection_committed"!==o.phase)throw new RuntimeCommandJournalConflictError("MCP release requires a committed effect");if(!o.mcpDesiredStateCommit)throw new RuntimeCommandJournalConflictError("MCP release lacks a durable desired-state commit");if("service"===e.source&&!o.messageBinding)throw new RuntimeCommandJournalConflictError("Service MCP release lacks a Message Service request binding");const r=releaseFromCommit(o.mcpDesiredStateCommit,e,new Date(this.now()).toISOString());if(o.mcpLiveRelease){if(sameMcpLiveRelease(o.mcpLiveRelease,r))return o;throw new RuntimeCommandJournalConflictError("MCP release conflicts with journal")}const i={...o,mcpLiveRelease:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async promoteMcpDesiredStateActive(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP active promotion has no command record");const o=t.records[n],r=activeFromRelease(o.mcpLiveRelease,e,new Date(this.now()).toISOString());if(o.mcpActiveDesiredState){if(sameMcpActiveDesiredState(o.mcpActiveDesiredState,r))return o;throw new RuntimeCommandJournalConflictError("MCP active desired state conflicts with journal")}const i={...o,mcpActiveDesiredState:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async acceptMessageRequest(e){return await this.update(t=>{let n=t.records.find(t=>t.operationId===e.operationId);if(n||(n={operationId:e.operationId,command:e.command,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,phase:"prepared",revision:1,updatedAt:new Date(this.now()).toISOString()},t.records.push(n)),n.command!==e.command||n.paramsHash!==e.paramsHash||n.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("message request conflicts with command journal");const o={requestMessageId:e.requestMessageId,requestHash:e.requestHash,acceptedAt:new Date(this.now()).toISOString()};if(n.messageBinding&&(n.messageBinding.requestMessageId!==o.requestMessageId||n.messageBinding.requestHash!==o.requestHash))throw new RuntimeCommandJournalConflictError("operation reused by a different message request");return n.messageBinding??=o,n})}async observeCancellation(e){return await this.update(t=>{const n=t.cancellations.find(t=>t.eventId===e.eventId);if(n){if(n.operationId!==e.operationId||n.requestMessageId!==e.requestMessageId||n.requestHash!==e.requestHash)throw new RuntimeCommandJournalConflictError("operation cancellation observation conflict");return n}const o=t.cancellations.find(t=>t.operationId===e.operationId);if(o&&(o.requestMessageId!==e.requestMessageId||o.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("operation cancellation request correlation conflict");const r={...e,observedAt:new Date(this.now()).toISOString()};return t.cancellations.push(r),r})}async prepare(e){return await this.update(t=>{const n=t.records.find(t=>t.operationId===e.operationId);if(n){if(n.command!==e.command||n.paramsHash!==e.paramsHash)throw new RuntimeCommandJournalConflictError("operation identity reused with different command or params hash");if(n.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("operation belongs to a different runtime epoch");return n}const o={...e,phase:"prepared",revision:1,updatedAt:new Date(this.now()).toISOString()};return t.records.push(o),o})}async transition(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision)throw new RuntimeCommandJournalConflictError("journal revision conflict");const r="effect_committed"===o.phase&&"effect_committed"===e.phase&&void 0!==e.agentProjection&&void 0===o.agentProjectionIntent&&"pending"===o.agentProjectionPreparationFailure?.state;if("projection_committed"===o.phase||"failed"===o.phase||"outcome_unknown"===o.phase||"effect_committed"===o.phase&&"projection_committed"!==e.phase&&!r)throw new RuntimeCommandJournalConflictError("terminal journal record is immutable");if(void 0!==e.projectionSnapshot&&"projection_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("projection receipt requires projection_committed phase");if(void 0!==e.agentProjection&&"effect_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("agent projection intent requires effect_committed phase");const i=void 0===e.agentProjection?void 0:normalizeAgentProjectionPreparation(e.agentProjection,o);if(r&&i&&!sameProjectionPreparationBase(o.agentProjectionPreparationFailure,i))throw new RuntimeCommandJournalConflictError("agent projection preparation conflicts with committed operation");const{agentProjectionIntent:a,agentProjectionPreparationFailure:s,...c}=o,d={...void 0===i?o:c,phase:e.phase,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString(),...void 0===e.result?{}:persistedResult(o.command,e.result),...void 0===e.projectionSnapshot?{}:{projectionReceipt:projectionReceipt(e.projectionSnapshot)},...void 0===i?{}:isAgentProjectionIntent(i)?{agentProjectionIntent:i}:{agentProjectionPreparationFailure:i},...void 0===e.errorCode?{}:{errorCode:e.errorCode}};return t.records[n]=d,d})}async recordAgentProjectionIntent(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision||"effect_committed"!==o.phase)throw new RuntimeCommandJournalConflictError("projection intent cannot mutate this command journal record");const r=normalizeAgentProjectionIntent(e.intent,o);if(o.agentProjectionIntent){if(canonicalizeJcs(o.agentProjectionIntent)!==canonicalizeJcs(r))throw new RuntimeCommandJournalConflictError("agent projection intent conflicts with committed operation");return o}if("pending"!==o.agentProjectionPreparationFailure?.state||!sameProjectionPreparationBase(o.agentProjectionPreparationFailure,r))throw new RuntimeCommandJournalConflictError("projection intent requires matching retryable preparation failure");const{agentProjectionPreparationFailure:i,...a}=o,s={...a,agentProjectionIntent:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=s,s})}async recordAgentProjectionBlocked(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision||"effect_committed"!==o.phase||!o.agentProjectionIntent||o.agentProjectionIntent.projectionRevision!==e.revision)throw new RuntimeCommandJournalConflictError("blocked projection cannot mutate this command journal record");if(o.agentProjectionTerminal){if("blocked"===o.agentProjectionTerminal.state&&o.agentProjectionTerminal.revision===e.revision)return o;throw new RuntimeCommandJournalConflictError("agent projection terminal conflicts with committed operation")}const r=agentProjectionTerminalReceipt(e.revision),i={...o,agentProjectionTerminal:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async compact(){return await this.update(e=>{const t=this.now()-this.retentionMs,n=e.records.length;e.records=e.records.filter(e=>"prepared"===e.phase||"started"===e.phase||Date.parse(e.updatedAt)>=t);const o=e.cancellations.length;return e.cancellations=e.cancellations.filter(e=>Date.parse(e.observedAt)>=t),n-e.records.length+o-e.cancellations.length})}async read(){const e=await this.readLegacy(),t=new Map;if(e){t.set("meta:lineage",{value:e.lineage,logicalRevision:e.lineage.journalGeneration,publisherEpoch:"0"});for(const n of e.records)t.set(`record:${n.operationId}`,{value:n,logicalRevision:String(n.revision),publisherEpoch:"0"});for(const n of e.cancellations)t.set(`cancellation:${n.eventId}`,{value:n,logicalRevision:"1",publisherEpoch:"0"})}const n=await this.segments.reduce(t),o=n.get("meta:lineage")?.value;return{version:2,lineage:o??emptyDocument().lineage,records:[...n.entries()].filter(([e])=>e.startsWith("record:")).map(([,e])=>validateRecordReceipts(migrateLegacyCommittedPhase(e.value))),cancellations:[...n.entries()].filter(([e])=>e.startsWith("cancellation:")).map(([,e])=>e.value)}}async readLegacy(){try{const e=JSON.parse(await readFile(this.file,"utf8"));if(!Array.isArray(e.records))throw new Error("unsupported command journal");const t=e.records.map(e=>validateRecordReceipts(migrateLegacyCommittedPhase(e)));if(1===e.version)return{...emptyDocument(),records:t};if(2!==e.version||!e.lineage?.journalStoreId||!/^[1-9][0-9]*$/.test(e.lineage.journalGeneration))throw new Error("unsupported command journal");return{...e,records:t,cancellations:Array.isArray(e.cancellations)?e.cancellations:[]}}catch(e){if("ENOENT"===e.code)return null;throw e}}async update(e){return await withOwnedDirectoryLock(`${this.file}.lock`,async t=>{const n=await this.segments.authorityFloor(),o=await this.read(),r=structuredClone(o),i=e(o),a=[];return a.push({key:"meta:lineage",logicalRevision:o.lineage.journalGeneration,value:o.lineage}),diffValues(r.records,o.records,e=>e.operationId,e=>String(e.revision),"record:",a),diffValues(r.cancellations,o.cancellations,e=>e.eventId,()=>"1","cancellation:",a),await(this.publication.beforePublish?.(t.epoch)),await this.segments.publish(t.epoch,a,n),await this.segments.prune(),i},{...this.publication.lockOptions,epochFloor:()=>this.segments.authorityFloor()})}}function diffValues(e,t,n,o,r,i){const a=new Map(e.map(e=>[n(e),e])),s=new Map(t.map(e=>[n(e),e]));for(const[e,t]of s)i.push({key:`${r}${e}`,logicalRevision:o(t),value:t});for(const[e,t]of a)s.has(e)||i.push({key:`${r}${e}`,logicalRevision:o(t),tombstone:!0})}function migrateLegacyCommittedPhase(e){return{...e,phase:"committed"===e.phase?"effect_committed":e.phase}}export class InMemoryRuntimeCommandJournal{records=new Map;cancellations=new Map;async get(e){return this.records.get(e)??null}async getCancellation(e){return[...this.cancellations.values()].find(t=>t.operationId===e)??null}async listMcpLiveReleases(){return Object.freeze([...this.records.values()].flatMap(e=>e.mcpLiveRelease?[e.mcpLiveRelease]:[]))}async listMcpActiveDesiredStates(){return Object.freeze([...this.records.values()].flatMap(e=>e.mcpActiveDesiredState?[e.mcpActiveDesiredState]:[]))}async recordMcpDesiredStateCommit(e){const t=normalizeMcpDesiredStateCommit(e),n=this.records.get(t.operationId);if(!n)throw new RuntimeCommandJournalConflictError("MCP desired state has no command record");if(assertMcpCommitBindsRecord(t,n),"started"!==n.phase){if(sameMcpDesiredStateCommit(n.mcpDesiredStateCommit,t))return n;throw new RuntimeCommandJournalConflictError("MCP desired state commit crossed its journal boundary")}if(n.mcpDesiredStateCommit){if(sameMcpDesiredStateCommit(n.mcpDesiredStateCommit,t))return n;throw new RuntimeCommandJournalConflictError("MCP desired state commit conflicts with journal")}const o={...n,mcpDesiredStateCommit:t,revision:n.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(t.operationId,o),o}async releaseMcpLiveEffect(e){const t=this.records.get(e.operationId);if(!t)throw new RuntimeCommandJournalConflictError("MCP release has no command record");if("effect_committed"!==t.phase&&"projection_committed"!==t.phase)throw new RuntimeCommandJournalConflictError("MCP release requires a committed effect");if(!t.mcpDesiredStateCommit)throw new RuntimeCommandJournalConflictError("MCP release lacks a durable desired-state commit");if("service"===e.source&&!t.messageBinding)throw new RuntimeCommandJournalConflictError("Service MCP release lacks a Message Service request binding");const n=releaseFromCommit(t.mcpDesiredStateCommit,e,(new Date).toISOString());if(t.mcpLiveRelease){if(sameMcpLiveRelease(t.mcpLiveRelease,n))return t;throw new RuntimeCommandJournalConflictError("MCP release conflicts with journal")}const o={...t,mcpLiveRelease:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,o),o}async promoteMcpDesiredStateActive(e){const t=this.records.get(e.operationId);if(!t)throw new RuntimeCommandJournalConflictError("MCP active promotion has no command record");const n=activeFromRelease(t.mcpLiveRelease,e,(new Date).toISOString());if(t.mcpActiveDesiredState){if(sameMcpActiveDesiredState(t.mcpActiveDesiredState,n))return t;throw new RuntimeCommandJournalConflictError("MCP active desired state conflicts with journal")}const o={...t,mcpActiveDesiredState:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,o),o}async acceptMessageRequest(e){let t=this.records.get(e.operationId);if(t||(t={operationId:e.operationId,command:e.command,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,phase:"prepared",revision:1,updatedAt:(new Date).toISOString()},this.records.set(e.operationId,t)),t.command!==e.command||t.paramsHash!==e.paramsHash||t.runtimeEpoch!==e.runtimeEpoch||t.messageBinding&&(t.messageBinding.requestMessageId!==e.requestMessageId||t.messageBinding.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("message request conflicts with command journal");return t.messageBinding??={requestMessageId:e.requestMessageId,requestHash:e.requestHash,acceptedAt:(new Date).toISOString()},t}async observeCancellation(e){const t=this.cancellations.get(e.eventId);if(t){if(t.operationId!==e.operationId||t.requestMessageId!==e.requestMessageId||t.requestHash!==e.requestHash)throw new RuntimeCommandJournalConflictError("operation cancellation observation conflict");return t}const n=[...this.cancellations.values()].find(t=>t.operationId===e.operationId);if(n&&(n.requestMessageId!==e.requestMessageId||n.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("operation cancellation request correlation conflict");const o={...e,observedAt:(new Date).toISOString()};return this.cancellations.set(e.eventId,o),o}async prepare(e){const t=this.records.get(e.operationId);if(t){if(t.command!==e.command||t.paramsHash!==e.paramsHash||t.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("operation identity conflict");return t}const n={...e,phase:"prepared",revision:1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,n),n}async transition(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision)throw new RuntimeCommandJournalConflictError("revision conflict");const n="effect_committed"===t.phase&&"effect_committed"===e.phase&&void 0!==e.agentProjection&&void 0===t.agentProjectionIntent&&"pending"===t.agentProjectionPreparationFailure?.state;if("projection_committed"===t.phase||"failed"===t.phase||"outcome_unknown"===t.phase||"effect_committed"===t.phase&&"projection_committed"!==e.phase&&!n)throw new RuntimeCommandJournalConflictError("terminal journal record is immutable");if(void 0!==e.projectionSnapshot&&"projection_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("projection receipt requires projection_committed phase");if(void 0!==e.agentProjection&&"effect_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("agent projection intent requires effect_committed phase");const o=void 0===e.agentProjection?void 0:normalizeAgentProjectionPreparation(e.agentProjection,t);if(n&&o&&!sameProjectionPreparationBase(t.agentProjectionPreparationFailure,o))throw new RuntimeCommandJournalConflictError("agent projection preparation conflicts with committed operation");const{agentProjectionIntent:r,agentProjectionPreparationFailure:i,...a}=t,s={...void 0===o?t:a,phase:e.phase,revision:t.revision+1,updatedAt:(new Date).toISOString(),...void 0===e.result?{}:persistedResult(t.command,e.result),...void 0===e.projectionSnapshot?{}:{projectionReceipt:projectionReceipt(e.projectionSnapshot)},...void 0===o?{}:isAgentProjectionIntent(o)?{agentProjectionIntent:o}:{agentProjectionPreparationFailure:o},...void 0===e.errorCode?{}:{errorCode:e.errorCode}};return this.records.set(e.operationId,s),s}async recordAgentProjectionIntent(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision||"effect_committed"!==t.phase)throw new RuntimeCommandJournalConflictError("projection intent cannot mutate this command journal record");const n=normalizeAgentProjectionIntent(e.intent,t);if(t.agentProjectionIntent){if(canonicalizeJcs(t.agentProjectionIntent)!==canonicalizeJcs(n))throw new RuntimeCommandJournalConflictError("agent projection intent conflicts with committed operation");return t}if("pending"!==t.agentProjectionPreparationFailure?.state||!sameProjectionPreparationBase(t.agentProjectionPreparationFailure,n))throw new RuntimeCommandJournalConflictError("projection intent requires matching retryable preparation failure");const{agentProjectionPreparationFailure:o,...r}=t,i={...r,agentProjectionIntent:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,i),i}async recordAgentProjectionBlocked(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision||"effect_committed"!==t.phase||!t.agentProjectionIntent||t.agentProjectionIntent.projectionRevision!==e.revision)throw new RuntimeCommandJournalConflictError("blocked projection cannot mutate this command journal record");if(t.agentProjectionTerminal){if("blocked"===t.agentProjectionTerminal.state&&t.agentProjectionTerminal.revision===e.revision)return t;throw new RuntimeCommandJournalConflictError("agent projection terminal conflicts with committed operation")}const n={...t,agentProjectionTerminal:agentProjectionTerminalReceipt(e.revision),revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,n),n}async compact(){return 0}}function persistedResult(e,t){return"canonical_result"===runtimeCommandResultPersistencePolicy(e)?{result:t}:{resultReceipt:{policy:"hash_only",resultHash:runtimeCommandResultHash(t),redacted:{kind:runtimeCommandIsReplayableQuery(e)?"query_result":"mutation_result",command:e}}}}function projectionReceipt(e){if("synced"!==e.state||!/^(0|[1-9][0-9]*)$/.test(e.revision))throw new Error("invalid projection revision");if(void 0!==e.platformAgentId&&!/^[a-zA-Z0-9._:-]{1,256}$/.test(e.platformAgentId))throw new Error("invalid projection platform agent id");const t=void 0===e.mcpReceipt?void 0:normalizeMcpProjectionReceipt(e.mcpReceipt),n=Object.freeze({revision:e.revision,state:"synced",..."string"==typeof e.platformAgentId&&e.platformAgentId?{platformAgentId:e.platformAgentId}:{},...t?{mcpReceipt:t}:{}});return Object.freeze({schemaVersion:1,revision:n.revision,snapshot:n,snapshotHash:runtimeCommandResultHash(n)})}function validateRecordReceipts(e){if(void 0!==e.agentProjectionIntent&&void 0!==e.agentProjectionPreparationFailure)throw new Error("agent projection journal record is ambiguous");const t=void 0!==e.agentProjectionIntent?{...e,agentProjectionIntent:normalizeAgentProjectionIntent(e.agentProjectionIntent,e)}:void 0!==e.agentProjectionPreparationFailure?{...e,agentProjectionPreparationFailure:normalizeAgentProjectionPreparation(e.agentProjectionPreparationFailure,e)}:e,n=void 0===t.agentProjectionTerminal?t:{...t,agentProjectionTerminal:normalizeAgentProjectionTerminal(t.agentProjectionTerminal)};if(n.agentProjectionTerminal&&(!n.agentProjectionIntent||"effect_committed"!==n.phase||n.agentProjectionIntent.projectionRevision!==n.agentProjectionTerminal.revision))throw new Error("invalid agent projection terminal receipt");const o=void 0===n.mcpDesiredStateCommit?void 0:normalizeMcpDesiredStateCommit(n.mcpDesiredStateCommit);o&&assertMcpCommitBindsRecord(o,n);const r=void 0===n.mcpLiveRelease?void 0:normalizeMcpLiveRelease(n.mcpLiveRelease);if(r&&(!o||!sameMcpDesiredStateCommit(o,r)||"effect_committed"!==n.phase&&"projection_committed"!==n.phase||"service"===r.source&&!n.messageBinding))throw new Error("invalid MCP live release receipt");const i=void 0===n.mcpActiveDesiredState?void 0:normalizeMcpActiveDesiredState(n.mcpActiveDesiredState);if(i&&(!r||!sameMcpDesiredStateCommit(i,r)||i.effectRecordHash!==r.effectRecordHash||i.releasedAt!==r.releasedAt))throw new Error("invalid MCP active desired-state receipt");const a={...n,...o?{mcpDesiredStateCommit:o}:{},...r?{mcpLiveRelease:r}:{},...i?{mcpActiveDesiredState:i}:{}};if(!a.projectionReceipt)return a;const s=a.projectionReceipt,c=["revision","state",...void 0===s.snapshot?.platformAgentId?[]:["platformAgentId"],...void 0===s.snapshot?.mcpReceipt?[]:["mcpReceipt"]].sort().join(",");if("revision,schemaVersion,snapshot,snapshotHash"!==Object.keys(s).sort().join(",")||Object.keys(s.snapshot??{}).sort().join(",")!==c||"projection_committed"!==a.phase||1!==s.schemaVersion||!/^(0|[1-9][0-9]*)$/.test(s.revision)||"synced"!==s.snapshot?.state||s.snapshot.revision!==s.revision)throw new Error("invalid projection receipt");const d=projectionReceipt(s.snapshot),m=d.snapshot.mcpReceipt;if(m&&(m.operationId!==a.operationId||m.method!==a.command||m.runtimeEpoch!==a.runtimeEpoch))throw new Error("MCP projection receipt does not bind journal operation");if(d.snapshotHash!==s.snapshotHash)throw new Error("invalid projection receipt hash");return{...a,projectionReceipt:d}}function normalizeMcpDesiredStateCommit(e){if(!e||"object"!=typeof e||"desiredStateHash,desiredStateRevision,method,operationId,paramsHash,runtimeEpoch,schemaVersion,server,source"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||"service"!==e.source&&"acp_local"!==e.source||!safeReceiptIdentifier(e.operationId)||"mcp.set"!==e.method&&"mcp.unset"!==e.method||!isSha256(e.paramsHash)||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||!/^(0|[1-9][0-9]*)$/.test(e.desiredStateRevision)||!isSha256(e.desiredStateHash))throw new Error("invalid MCP desired-state commit");return Object.freeze({schemaVersion:1,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:normalizeMcpReleaseServer(e.server)})}function normalizeMcpLiveRelease(e){if(!e||"object"!=typeof e)throw new Error("invalid MCP live release receipt");const t=["desiredStateHash","desiredStateRevision","method","operationId","paramsHash","releasedAt","runtimeEpoch","schemaVersion","server","source",..."service"===e.source?["effectRecordHash"]:[]].sort().join(",");if(Object.keys(e).sort().join(",")!==t||"service"===e.source&&!isSha256(e.effectRecordHash)||"acp_local"===e.source&&void 0!==e.effectRecordHash||"string"!=typeof e.releasedAt||!Number.isFinite(Date.parse(e.releasedAt)))throw new Error("invalid MCP live release receipt");const n=normalizeMcpDesiredStateCommit({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server});return Object.freeze({...n,...e.effectRecordHash?{effectRecordHash:e.effectRecordHash}:{},releasedAt:e.releasedAt})}function normalizeMcpActiveDesiredState(e){if(!e||"object"!=typeof e)throw new Error("invalid MCP active desired-state receipt");const t=["activatedAt","desiredStateHash","desiredStateRevision","method","operationId","paramsHash","releasedAt","runtimeEpoch","schemaVersion","server","source",..."service"===e.source?["effectRecordHash","terminalMessageId"]:[]].sort().join(",");if(Object.keys(e).sort().join(",")!==t||"string"!=typeof e.activatedAt||!Number.isFinite(Date.parse(e.activatedAt))||"service"===e.source&&!safeReceiptIdentifier(e.terminalMessageId))throw new Error("invalid MCP active desired-state receipt");const n=normalizeMcpLiveRelease({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server,releasedAt:e.releasedAt,...e.effectRecordHash?{effectRecordHash:e.effectRecordHash}:{}});return Object.freeze({...n,...e.terminalMessageId?{terminalMessageId:e.terminalMessageId}:{},activatedAt:e.activatedAt})}function normalizeMcpReleaseServer(e){const t=e&&"object"==typeof e?Object.keys(e).sort().join(","):"",n=!0===e?.removed;if(!(t===(n?"name,removed":void 0===e?.credentialRef?"definitionRef,definitionSha256,name,version":"credentialRef,definitionRef,definitionSha256,name,version")&&safeReceiptIdentifier(e?.name)&&(n||safeMcpReference(e?.definitionRef)&&safeMcpReference(e?.version)&&isSha256(e?.definitionSha256)&&(void 0===e?.credentialRef||safeMcpReference(e.credentialRef)))))throw new Error("invalid MCP desired-state server identity");return Object.freeze(n?{name:e.name,removed:!0}:{name:e.name,definitionRef:e.definitionRef,version:e.version,definitionSha256:e.definitionSha256,...e.credentialRef?{credentialRef:e.credentialRef}:{}})}function assertMcpCommitBindsRecord(e,t){if(e.operationId!==t.operationId||e.method!==t.command||e.paramsHash!==t.paramsHash||e.runtimeEpoch!==t.runtimeEpoch)throw new RuntimeCommandJournalConflictError("MCP desired-state commit does not bind command journal")}function releaseFromCommit(e,t,n){if(t.operationId!==e.operationId||t.source!==e.source||"service"===t.source&&!isSha256(t.effectRecordHash)||"acp_local"===t.source&&void 0!==t.effectRecordHash)throw new RuntimeCommandJournalConflictError("MCP live release proof does not bind desired state");return Object.freeze({...e,...t.effectRecordHash?{effectRecordHash:t.effectRecordHash}:{},releasedAt:n})}function activeFromRelease(e,t,n){if(!(e&&t.operationId===e.operationId&&t.source===e.source&&Number.isFinite(Date.parse(n))&&("service"!==t.source||isSha256(t.effectRecordHash)&&t.effectRecordHash===e.effectRecordHash&&safeReceiptIdentifier(t.terminalMessageId))&&("acp_local"!==t.source||void 0===t.effectRecordHash&&void 0===t.terminalMessageId)))throw new RuntimeCommandJournalConflictError("MCP active promotion does not bind terminal release proof");return Object.freeze({...e,...t.terminalMessageId?{terminalMessageId:t.terminalMessageId}:{},activatedAt:n})}function sameMcpDesiredStateCommit(e,t){if(!e||!t)return!1;const n=e=>({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function sameMcpLiveRelease(e,t){const n=e=>({...e,releasedAt:""});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function sameMcpActiveDesiredState(e,t){const n=e=>({...e,releasedAt:"",activatedAt:""});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function normalizeMcpProjectionReceipt(e){if(!e||"object"!=typeof e||"desiredStateHash,desiredStateRevision,method,operationId,runtimeEpoch,schemaVersion,server"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||!safeReceiptIdentifier(e.operationId)||"mcp.set"!==e.method&&"mcp.unset"!==e.method||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||!/^(0|[1-9][0-9]*)$/.test(e.desiredStateRevision)||!isSha256(e.desiredStateHash))throw new Error("invalid MCP projection receipt");const t=e.server;if((t&&"object"==typeof t?Object.keys(t).sort().join(","):"")!==(!0===t?.removed?"name,removed":void 0===t?.definitionRef?"name":"definitionRef,definitionSha256,name")||!safeReceiptIdentifier(t?.name)||void 0!==t?.definitionRef&&(!safeReceiptIdentifier(t.definitionRef)||!isSha256(t.definitionSha256)))throw new Error("invalid MCP projection server receipt");return Object.freeze({schemaVersion:1,operationId:e.operationId,method:e.method,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:Object.freeze(!0===t.removed?{name:t.name,removed:!0}:void 0===t.definitionRef?{name:t.name}:{name:t.name,definitionRef:t.definitionRef,definitionSha256:t.definitionSha256})})}function isSha256(e){return"string"==typeof e&&/^[a-f0-9]{64}$/.test(e)}function safeReceiptIdentifier(e){return"string"==typeof e&&/^[a-zA-Z0-9._:@/-]{1,512}$/.test(e)&&!/(authorization|bearer|credential|secret|token|password|private(?:_|-)?key|headers?|env)/i.test(e)}function safeMcpReference(e){return"string"==typeof e&&/^[a-zA-Z0-9._:@/-]{1,512}$/.test(e)&&!/(authorization|bearer|secret|token|password|private(?:_|-)?key|headers?|env)/i.test(e)}function agentProjectionTerminalReceipt(e){if(!/^(0|[1-9][0-9]*)$/.test(e))throw new Error("invalid agent projection terminal revision");return Object.freeze({schemaVersion:1,state:"blocked",revision:e})}function normalizeAgentProjectionTerminal(e){if("revision,schemaVersion,state"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||"blocked"!==e.state)throw new Error("invalid agent projection terminal receipt");return agentProjectionTerminalReceipt(e.revision)}function normalizeAgentProjectionIntent(e,t){const n=Object.keys(e).sort(),o=["canonicalHash","coreResultHash","method","operationId","projectionRevision","runtimeAgentUid","runtimeEpoch","runtimeLocalKey","schemaVersion","snapshotRef",...void 0===e.expectedPlatformAgentId?[]:["expectedPlatformAgentId"],...void 0===e.removedBindings?[]:["removedBindings"]].sort();if(n.length!==o.length||n.some((e,t)=>e!==o[t])||1!==e.schemaVersion||!["agent.create","agent.update","agent.delete","agent.applyTemplate"].includes(e.method)||!nonEmptyText(e.operationId)||!nonEmptyText(e.runtimeAgentUid)||!nonEmptyText(e.runtimeLocalKey)||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||e.operationId!==t.operationId||e.method!==t.command||e.runtimeEpoch!==t.runtimeEpoch||void 0!==e.expectedPlatformAgentId&&!nonEmptyText(e.expectedPlatformAgentId)||!/^[0-9a-f]{64}$/.test(e.coreResultHash)||!/^(0|[1-9][0-9]*)$/.test(e.projectionRevision)||!/^[0-9a-f]{64}$/.test(e.canonicalHash)||!nonEmptyText(e.snapshotRef)||e.snapshotRef!==projectionSnapshotRef(e.runtimeEpoch,e.projectionRevision,e.canonicalHash)||void 0!==e.removedBindings&&(!Number.isSafeInteger(e.removedBindings)||e.removedBindings<0))throw new Error("invalid agent projection intent");return Object.freeze({...e})}function normalizeAgentProjectionPreparation(e,t){if(isAgentProjectionIntent(e))return normalizeAgentProjectionIntent(e,t);const n=e,o=Object.keys(n).sort(),r=["coreResultHash","errorKind","method","operationId","runtimeAgentUid","runtimeEpoch","runtimeLocalKey","schemaVersion","state",...void 0===n.expectedPlatformAgentId?[]:["expectedPlatformAgentId"],...void 0===n.removedBindings?[]:["removedBindings"]].sort();if(o.length!==r.length||o.some((e,t)=>e!==r[t])||1!==n.schemaVersion||!["agent.create","agent.update","agent.delete","agent.applyTemplate"].includes(n.method)||n.operationId!==t.operationId||n.method!==t.command||n.runtimeEpoch!==t.runtimeEpoch||!nonEmptyText(n.runtimeAgentUid)||!nonEmptyText(n.runtimeLocalKey)||!/^(0|[1-9][0-9]*)$/.test(n.runtimeEpoch)||!/^[0-9a-f]{64}$/.test(n.coreResultHash)||"pending"!==n.state&&"blocked"!==n.state||"snapshot_retryable"!==n.errorKind&&"snapshot_nonretryable"!==n.errorKind||"pending"===n.state!=("snapshot_retryable"===n.errorKind)||void 0!==n.expectedPlatformAgentId&&!nonEmptyText(n.expectedPlatformAgentId)||void 0!==n.removedBindings&&(!Number.isSafeInteger(n.removedBindings)||n.removedBindings<0))throw new Error("invalid agent projection preparation failure");return Object.freeze({...n})}function sameProjectionPreparationBase(e,t){return e.operationId===t.operationId&&e.method===t.method&&e.runtimeEpoch===t.runtimeEpoch&&e.runtimeAgentUid===t.runtimeAgentUid&&e.runtimeLocalKey===t.runtimeLocalKey&&e.coreResultHash===t.coreResultHash&&e.expectedPlatformAgentId===t.expectedPlatformAgentId&&e.removedBindings===t.removedBindings}function isAgentProjectionIntent(e){return"projectionRevision"in e}function projectionSnapshotRef(e,t,n){return`runtime-agent-projection:v1:${e}:${t}:${n}`}function nonEmptyText(e){return"string"==typeof e&&e.length>0}
1
+ import{createHash,randomUUID}from"node:crypto";import{readFile}from"node:fs/promises";import{canonicalizeJcs}from"@beeos-ai/beeos-types/runtime";import{createRuntimeAppendTimingRecorder}from"./runtime-append-timing.js";import{withOwnedDirectoryLock}from"./owned-directory-lock.js";import{MonotonicJournalSegments}from"./monotonic-journal-segments.js";const HASH_ONLY_RESULT_COMMANDS=new Set(["session.load","session.history","session.list","agent.listProjection","skills.list","models.list","cron.list","cron.status","cron.runs","mcp.list","terminal.ping","terminal.wait"]),SAFE_CANONICAL_RESULT_COMMANDS=new Set(["session.clear","session.setMode","session.setModel","session.cancel","skills.install","skills.uninstall","skills.setEnabled","cron.remove","mcp.set","mcp.unset","terminal.input","terminal.resize","terminal.close","terminal.release","terminal.kill","canvas.toggle","canvas.clear","canvas.reference","canvas.dimensions"]);export const runtimeCommandResultPersistencePolicy=e=>SAFE_CANONICAL_RESULT_COMMANDS.has(e)?"canonical_result":"hash_only";export const runtimeCommandIsReplayableQuery=e=>HASH_ONLY_RESULT_COMMANDS.has(e);export const runtimeCommandResultHash=e=>createHash("sha256").update(canonicalizeJcs(e)).digest("hex");const emptyDocument=()=>({version:2,lineage:{journalStoreId:randomUUID(),journalGeneration:"1"},records:[],cancellations:[]});export class RuntimeCommandJournalConflictError extends Error{constructor(e){super(e),this.name="RuntimeCommandJournalConflictError"}}export class FileRuntimeCommandJournal{file;retentionMs;now;publication;segments;constructor(e,t=6048e5,n=Date.now,o={}){this.file=e,this.retentionMs=t,this.now=n,this.publication=o,this.segments=new MonotonicJournalSegments(e)}async lineage(){return await this.update(e=>({...e.lineage}))}async advanceLineageGeneration(e){return await this.update(t=>{if(t.lineage.journalStoreId!==e.journalStoreId)throw new RuntimeCommandJournalConflictError("journal store changed during incarnation advance");const n=BigInt(t.lineage.journalGeneration),o=BigInt(e.floor),r=(n>o?n:o)+1n;if(r>9223372036854775807n)throw new RuntimeCommandJournalConflictError("journal generation overflow");return t.lineage={journalStoreId:t.lineage.journalStoreId,journalGeneration:String(r)},{...t.lineage}})}async get(e){return(await this.read()).records.find(t=>t.operationId===e)??null}async getCancellation(e){return(await this.read()).cancellations.find(t=>t.operationId===e)??null}async listMcpLiveReleases(){const e=await this.read();return Object.freeze(e.records.flatMap(e=>e.mcpLiveRelease?[e.mcpLiveRelease]:[]))}async listMcpActiveDesiredStates(){const e=await this.read();return Object.freeze(e.records.flatMap(e=>e.mcpActiveDesiredState?[e.mcpActiveDesiredState]:[]))}async recordMcpDesiredStateCommit(e){const t=normalizeMcpDesiredStateCommit(e);return await this.update(e=>{const n=e.records.findIndex(e=>e.operationId===t.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP desired state has no command record");const o=e.records[n];if(assertMcpCommitBindsRecord(t,o),"started"!==o.phase){if(sameMcpDesiredStateCommit(o.mcpDesiredStateCommit,t))return o;throw new RuntimeCommandJournalConflictError("MCP desired state commit crossed its journal boundary")}if(o.mcpDesiredStateCommit){if(sameMcpDesiredStateCommit(o.mcpDesiredStateCommit,t))return o;throw new RuntimeCommandJournalConflictError("MCP desired state commit conflicts with journal")}const r={...o,mcpDesiredStateCommit:t,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return e.records[n]=r,r})}async releaseMcpLiveEffect(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP release has no command record");const o=t.records[n];if("effect_committed"!==o.phase&&"projection_committed"!==o.phase)throw new RuntimeCommandJournalConflictError("MCP release requires a committed effect");if(!o.mcpDesiredStateCommit)throw new RuntimeCommandJournalConflictError("MCP release lacks a durable desired-state commit");if("service"===e.source&&!o.messageBinding)throw new RuntimeCommandJournalConflictError("Service MCP release lacks a Message Service request binding");const r=releaseFromCommit(o.mcpDesiredStateCommit,e,new Date(this.now()).toISOString());if(o.mcpLiveRelease){if(sameMcpLiveRelease(o.mcpLiveRelease,r))return o;throw new RuntimeCommandJournalConflictError("MCP release conflicts with journal")}const i={...o,mcpLiveRelease:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async promoteMcpDesiredStateActive(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("MCP active promotion has no command record");const o=t.records[n],r=activeFromRelease(o.mcpLiveRelease,e,new Date(this.now()).toISOString());if(o.mcpActiveDesiredState){if(sameMcpActiveDesiredState(o.mcpActiveDesiredState,r))return o;throw new RuntimeCommandJournalConflictError("MCP active desired state conflicts with journal")}const i={...o,mcpActiveDesiredState:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async acceptMessageRequest(e){return await this.update(t=>{let n=t.records.find(t=>t.operationId===e.operationId);if(n||(n={operationId:e.operationId,command:e.command,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,phase:"prepared",revision:1,updatedAt:new Date(this.now()).toISOString()},t.records.push(n)),n.command!==e.command||n.paramsHash!==e.paramsHash||n.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("message request conflicts with command journal");const o={requestMessageId:e.requestMessageId,requestHash:e.requestHash,acceptedAt:new Date(this.now()).toISOString()};if(n.messageBinding&&(n.messageBinding.requestMessageId!==o.requestMessageId||n.messageBinding.requestHash!==o.requestHash))throw new RuntimeCommandJournalConflictError("operation reused by a different message request");return n.messageBinding??=o,n})}async observeCancellation(e){return await this.update(t=>{const n=t.cancellations.find(t=>t.eventId===e.eventId);if(n){if(n.operationId!==e.operationId||n.requestMessageId!==e.requestMessageId||n.requestHash!==e.requestHash)throw new RuntimeCommandJournalConflictError("operation cancellation observation conflict");return n}const o=t.cancellations.find(t=>t.operationId===e.operationId);if(o&&(o.requestMessageId!==e.requestMessageId||o.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("operation cancellation request correlation conflict");const r={...e,observedAt:new Date(this.now()).toISOString()};return t.cancellations.push(r),r})}async prepare(e){return await this.update(t=>{const n=t.records.find(t=>t.operationId===e.operationId);if(n){if(n.command!==e.command||n.paramsHash!==e.paramsHash)throw new RuntimeCommandJournalConflictError("operation identity reused with different command or params hash");if(n.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("operation belongs to a different runtime epoch");return n}const o={...e,phase:"prepared",revision:1,updatedAt:new Date(this.now()).toISOString()};return t.records.push(o),o})}async transition(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision)throw new RuntimeCommandJournalConflictError("journal revision conflict");const r="effect_committed"===o.phase&&"effect_committed"===e.phase&&void 0!==e.agentProjection&&void 0===o.agentProjectionIntent&&"pending"===o.agentProjectionPreparationFailure?.state;if("projection_committed"===o.phase||"failed"===o.phase||"outcome_unknown"===o.phase||"effect_committed"===o.phase&&"projection_committed"!==e.phase&&!r)throw new RuntimeCommandJournalConflictError("terminal journal record is immutable");if(void 0!==e.projectionSnapshot&&"projection_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("projection receipt requires projection_committed phase");if(void 0!==e.agentProjection&&"effect_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("agent projection intent requires effect_committed phase");const i=void 0===e.agentProjection?void 0:normalizeAgentProjectionPreparation(e.agentProjection,o);if(r&&i&&!sameProjectionPreparationBase(o.agentProjectionPreparationFailure,i))throw new RuntimeCommandJournalConflictError("agent projection preparation conflicts with committed operation");const{agentProjectionIntent:a,agentProjectionPreparationFailure:s,...c}=o,d={...void 0===i?o:c,phase:e.phase,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString(),...void 0===e.result?{}:persistedResult(o.command,e.result),...void 0===e.projectionSnapshot?{}:{projectionReceipt:projectionReceipt(e.projectionSnapshot)},...void 0===i?{}:isAgentProjectionIntent(i)?{agentProjectionIntent:i}:{agentProjectionPreparationFailure:i},...void 0===e.errorCode?{}:{errorCode:e.errorCode}};return t.records[n]=d,d})}async recordAgentProjectionIntent(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision||"effect_committed"!==o.phase)throw new RuntimeCommandJournalConflictError("projection intent cannot mutate this command journal record");const r=normalizeAgentProjectionIntent(e.intent,o);if(o.agentProjectionIntent){if(canonicalizeJcs(o.agentProjectionIntent)!==canonicalizeJcs(r))throw new RuntimeCommandJournalConflictError("agent projection intent conflicts with committed operation");return o}if("pending"!==o.agentProjectionPreparationFailure?.state||!sameProjectionPreparationBase(o.agentProjectionPreparationFailure,r))throw new RuntimeCommandJournalConflictError("projection intent requires matching retryable preparation failure");const{agentProjectionPreparationFailure:i,...a}=o,s={...a,agentProjectionIntent:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=s,s})}async recordAgentProjectionBlocked(e){return await this.update(t=>{const n=t.records.findIndex(t=>t.operationId===e.operationId);if(n<0)throw new RuntimeCommandJournalConflictError("operation was not prepared");const o=t.records[n];if(o.revision!==e.expectedRevision||"effect_committed"!==o.phase||!o.agentProjectionIntent||o.agentProjectionIntent.projectionRevision!==e.revision)throw new RuntimeCommandJournalConflictError("blocked projection cannot mutate this command journal record");if(o.agentProjectionTerminal){if("blocked"===o.agentProjectionTerminal.state&&o.agentProjectionTerminal.revision===e.revision)return o;throw new RuntimeCommandJournalConflictError("agent projection terminal conflicts with committed operation")}const r=agentProjectionTerminalReceipt(e.revision),i={...o,agentProjectionTerminal:r,revision:o.revision+1,updatedAt:new Date(this.now()).toISOString()};return t.records[n]=i,i})}async compact(){return await this.update(e=>{const t=this.now()-this.retentionMs,n=e.records.length;e.records=e.records.filter(e=>"prepared"===e.phase||"started"===e.phase||Date.parse(e.updatedAt)>=t);const o=e.cancellations.length;return e.cancellations=e.cancellations.filter(e=>Date.parse(e.observedAt)>=t),n-e.records.length+o-e.cancellations.length})}async read(){const e=await this.readLegacy(),t=new Map;if(e){t.set("meta:lineage",{value:e.lineage,logicalRevision:e.lineage.journalGeneration,publisherEpoch:"0"});for(const n of e.records)t.set(`record:${n.operationId}`,{value:n,logicalRevision:String(n.revision),publisherEpoch:"0"});for(const n of e.cancellations)t.set(`cancellation:${n.eventId}`,{value:n,logicalRevision:"1",publisherEpoch:"0"})}const n=await this.segments.reduce(t),o=n.get("meta:lineage")?.value;return{version:2,lineage:o??emptyDocument().lineage,records:[...n.entries()].filter(([e])=>e.startsWith("record:")).map(([,e])=>validateRecordReceipts(migrateLegacyCommittedPhase(e.value))),cancellations:[...n.entries()].filter(([e])=>e.startsWith("cancellation:")).map(([,e])=>e.value)}}async readLegacy(){try{const e=JSON.parse(await readFile(this.file,"utf8"));if(!Array.isArray(e.records))throw new Error("unsupported command journal");const t=e.records.map(e=>validateRecordReceipts(migrateLegacyCommittedPhase(e)));if(1===e.version)return{...emptyDocument(),records:t};if(2!==e.version||!e.lineage?.journalStoreId||!/^[1-9][0-9]*$/.test(e.lineage.journalGeneration))throw new Error("unsupported command journal");return{...e,records:t,cancellations:Array.isArray(e.cancellations)?e.cancellations:[]}}catch(e){if("ENOENT"===e.code)return null;throw e}}async update(e){const t=createRuntimeAppendTimingRecorder("journal.update",{file:this.file});try{return await withOwnedDirectoryLock(`${this.file}.lock`,async n=>{t.mark("lock");const o=await this.segments.authorityFloor();t.mark("floor");const r=await this.read();t.mark("read");const i=structuredClone(r),a=e(r),s=[];return s.push({key:"meta:lineage",logicalRevision:r.lineage.journalGeneration,value:r.lineage}),diffValues(i.records,r.records,e=>e.operationId,e=>String(e.revision),"record:",s),diffValues(i.cancellations,r.cancellations,e=>e.eventId,()=>"1","cancellation:",s),await(this.publication.beforePublish?.(n.epoch)),t.mark("before-publish"),await this.segments.publish(n.epoch,s,o),t.mark("publish"),await this.segments.prune(),t.mark("prune"),a},{...this.publication.lockOptions,epochFloor:()=>this.segments.authorityFloor()})}finally{t.finish()}}}function diffValues(e,t,n,o,r,i){const a=new Map(e.map(e=>[n(e),e])),s=new Map(t.map(e=>[n(e),e]));for(const[e,t]of s)i.push({key:`${r}${e}`,logicalRevision:o(t),value:t});for(const[e,t]of a)s.has(e)||i.push({key:`${r}${e}`,logicalRevision:o(t),tombstone:!0})}function migrateLegacyCommittedPhase(e){return{...e,phase:"committed"===e.phase?"effect_committed":e.phase}}export class InMemoryRuntimeCommandJournal{records=new Map;cancellations=new Map;async get(e){return this.records.get(e)??null}async getCancellation(e){return[...this.cancellations.values()].find(t=>t.operationId===e)??null}async listMcpLiveReleases(){return Object.freeze([...this.records.values()].flatMap(e=>e.mcpLiveRelease?[e.mcpLiveRelease]:[]))}async listMcpActiveDesiredStates(){return Object.freeze([...this.records.values()].flatMap(e=>e.mcpActiveDesiredState?[e.mcpActiveDesiredState]:[]))}async recordMcpDesiredStateCommit(e){const t=normalizeMcpDesiredStateCommit(e),n=this.records.get(t.operationId);if(!n)throw new RuntimeCommandJournalConflictError("MCP desired state has no command record");if(assertMcpCommitBindsRecord(t,n),"started"!==n.phase){if(sameMcpDesiredStateCommit(n.mcpDesiredStateCommit,t))return n;throw new RuntimeCommandJournalConflictError("MCP desired state commit crossed its journal boundary")}if(n.mcpDesiredStateCommit){if(sameMcpDesiredStateCommit(n.mcpDesiredStateCommit,t))return n;throw new RuntimeCommandJournalConflictError("MCP desired state commit conflicts with journal")}const o={...n,mcpDesiredStateCommit:t,revision:n.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(t.operationId,o),o}async releaseMcpLiveEffect(e){const t=this.records.get(e.operationId);if(!t)throw new RuntimeCommandJournalConflictError("MCP release has no command record");if("effect_committed"!==t.phase&&"projection_committed"!==t.phase)throw new RuntimeCommandJournalConflictError("MCP release requires a committed effect");if(!t.mcpDesiredStateCommit)throw new RuntimeCommandJournalConflictError("MCP release lacks a durable desired-state commit");if("service"===e.source&&!t.messageBinding)throw new RuntimeCommandJournalConflictError("Service MCP release lacks a Message Service request binding");const n=releaseFromCommit(t.mcpDesiredStateCommit,e,(new Date).toISOString());if(t.mcpLiveRelease){if(sameMcpLiveRelease(t.mcpLiveRelease,n))return t;throw new RuntimeCommandJournalConflictError("MCP release conflicts with journal")}const o={...t,mcpLiveRelease:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,o),o}async promoteMcpDesiredStateActive(e){const t=this.records.get(e.operationId);if(!t)throw new RuntimeCommandJournalConflictError("MCP active promotion has no command record");const n=activeFromRelease(t.mcpLiveRelease,e,(new Date).toISOString());if(t.mcpActiveDesiredState){if(sameMcpActiveDesiredState(t.mcpActiveDesiredState,n))return t;throw new RuntimeCommandJournalConflictError("MCP active desired state conflicts with journal")}const o={...t,mcpActiveDesiredState:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,o),o}async acceptMessageRequest(e){let t=this.records.get(e.operationId);if(t||(t={operationId:e.operationId,command:e.command,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,phase:"prepared",revision:1,updatedAt:(new Date).toISOString()},this.records.set(e.operationId,t)),t.command!==e.command||t.paramsHash!==e.paramsHash||t.runtimeEpoch!==e.runtimeEpoch||t.messageBinding&&(t.messageBinding.requestMessageId!==e.requestMessageId||t.messageBinding.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("message request conflicts with command journal");return t.messageBinding??={requestMessageId:e.requestMessageId,requestHash:e.requestHash,acceptedAt:(new Date).toISOString()},t}async observeCancellation(e){const t=this.cancellations.get(e.eventId);if(t){if(t.operationId!==e.operationId||t.requestMessageId!==e.requestMessageId||t.requestHash!==e.requestHash)throw new RuntimeCommandJournalConflictError("operation cancellation observation conflict");return t}const n=[...this.cancellations.values()].find(t=>t.operationId===e.operationId);if(n&&(n.requestMessageId!==e.requestMessageId||n.requestHash!==e.requestHash))throw new RuntimeCommandJournalConflictError("operation cancellation request correlation conflict");const o={...e,observedAt:(new Date).toISOString()};return this.cancellations.set(e.eventId,o),o}async prepare(e){const t=this.records.get(e.operationId);if(t){if(t.command!==e.command||t.paramsHash!==e.paramsHash||t.runtimeEpoch!==e.runtimeEpoch)throw new RuntimeCommandJournalConflictError("operation identity conflict");return t}const n={...e,phase:"prepared",revision:1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,n),n}async transition(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision)throw new RuntimeCommandJournalConflictError("revision conflict");const n="effect_committed"===t.phase&&"effect_committed"===e.phase&&void 0!==e.agentProjection&&void 0===t.agentProjectionIntent&&"pending"===t.agentProjectionPreparationFailure?.state;if("projection_committed"===t.phase||"failed"===t.phase||"outcome_unknown"===t.phase||"effect_committed"===t.phase&&"projection_committed"!==e.phase&&!n)throw new RuntimeCommandJournalConflictError("terminal journal record is immutable");if(void 0!==e.projectionSnapshot&&"projection_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("projection receipt requires projection_committed phase");if(void 0!==e.agentProjection&&"effect_committed"!==e.phase)throw new RuntimeCommandJournalConflictError("agent projection intent requires effect_committed phase");const o=void 0===e.agentProjection?void 0:normalizeAgentProjectionPreparation(e.agentProjection,t);if(n&&o&&!sameProjectionPreparationBase(t.agentProjectionPreparationFailure,o))throw new RuntimeCommandJournalConflictError("agent projection preparation conflicts with committed operation");const{agentProjectionIntent:r,agentProjectionPreparationFailure:i,...a}=t,s={...void 0===o?t:a,phase:e.phase,revision:t.revision+1,updatedAt:(new Date).toISOString(),...void 0===e.result?{}:persistedResult(t.command,e.result),...void 0===e.projectionSnapshot?{}:{projectionReceipt:projectionReceipt(e.projectionSnapshot)},...void 0===o?{}:isAgentProjectionIntent(o)?{agentProjectionIntent:o}:{agentProjectionPreparationFailure:o},...void 0===e.errorCode?{}:{errorCode:e.errorCode}};return this.records.set(e.operationId,s),s}async recordAgentProjectionIntent(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision||"effect_committed"!==t.phase)throw new RuntimeCommandJournalConflictError("projection intent cannot mutate this command journal record");const n=normalizeAgentProjectionIntent(e.intent,t);if(t.agentProjectionIntent){if(canonicalizeJcs(t.agentProjectionIntent)!==canonicalizeJcs(n))throw new RuntimeCommandJournalConflictError("agent projection intent conflicts with committed operation");return t}if("pending"!==t.agentProjectionPreparationFailure?.state||!sameProjectionPreparationBase(t.agentProjectionPreparationFailure,n))throw new RuntimeCommandJournalConflictError("projection intent requires matching retryable preparation failure");const{agentProjectionPreparationFailure:o,...r}=t,i={...r,agentProjectionIntent:n,revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,i),i}async recordAgentProjectionBlocked(e){const t=this.records.get(e.operationId);if(!t||t.revision!==e.expectedRevision||"effect_committed"!==t.phase||!t.agentProjectionIntent||t.agentProjectionIntent.projectionRevision!==e.revision)throw new RuntimeCommandJournalConflictError("blocked projection cannot mutate this command journal record");if(t.agentProjectionTerminal){if("blocked"===t.agentProjectionTerminal.state&&t.agentProjectionTerminal.revision===e.revision)return t;throw new RuntimeCommandJournalConflictError("agent projection terminal conflicts with committed operation")}const n={...t,agentProjectionTerminal:agentProjectionTerminalReceipt(e.revision),revision:t.revision+1,updatedAt:(new Date).toISOString()};return this.records.set(e.operationId,n),n}async compact(){return 0}}function persistedResult(e,t){return"canonical_result"===runtimeCommandResultPersistencePolicy(e)?{result:t}:{resultReceipt:{policy:"hash_only",resultHash:runtimeCommandResultHash(t),redacted:{kind:runtimeCommandIsReplayableQuery(e)?"query_result":"mutation_result",command:e}}}}function projectionReceipt(e){if("synced"!==e.state||!/^(0|[1-9][0-9]*)$/.test(e.revision))throw new Error("invalid projection revision");if(void 0!==e.platformAgentId&&!/^[a-zA-Z0-9._:-]{1,256}$/.test(e.platformAgentId))throw new Error("invalid projection platform agent id");const t=void 0===e.mcpReceipt?void 0:normalizeMcpProjectionReceipt(e.mcpReceipt),n=Object.freeze({revision:e.revision,state:"synced",..."string"==typeof e.platformAgentId&&e.platformAgentId?{platformAgentId:e.platformAgentId}:{},...t?{mcpReceipt:t}:{}});return Object.freeze({schemaVersion:1,revision:n.revision,snapshot:n,snapshotHash:runtimeCommandResultHash(n)})}function validateRecordReceipts(e){if(void 0!==e.agentProjectionIntent&&void 0!==e.agentProjectionPreparationFailure)throw new Error("agent projection journal record is ambiguous");const t=void 0!==e.agentProjectionIntent?{...e,agentProjectionIntent:normalizeAgentProjectionIntent(e.agentProjectionIntent,e)}:void 0!==e.agentProjectionPreparationFailure?{...e,agentProjectionPreparationFailure:normalizeAgentProjectionPreparation(e.agentProjectionPreparationFailure,e)}:e,n=void 0===t.agentProjectionTerminal?t:{...t,agentProjectionTerminal:normalizeAgentProjectionTerminal(t.agentProjectionTerminal)};if(n.agentProjectionTerminal&&(!n.agentProjectionIntent||"effect_committed"!==n.phase||n.agentProjectionIntent.projectionRevision!==n.agentProjectionTerminal.revision))throw new Error("invalid agent projection terminal receipt");const o=void 0===n.mcpDesiredStateCommit?void 0:normalizeMcpDesiredStateCommit(n.mcpDesiredStateCommit);o&&assertMcpCommitBindsRecord(o,n);const r=void 0===n.mcpLiveRelease?void 0:normalizeMcpLiveRelease(n.mcpLiveRelease);if(r&&(!o||!sameMcpDesiredStateCommit(o,r)||"effect_committed"!==n.phase&&"projection_committed"!==n.phase||"service"===r.source&&!n.messageBinding))throw new Error("invalid MCP live release receipt");const i=void 0===n.mcpActiveDesiredState?void 0:normalizeMcpActiveDesiredState(n.mcpActiveDesiredState);if(i&&(!r||!sameMcpDesiredStateCommit(i,r)||i.effectRecordHash!==r.effectRecordHash||i.releasedAt!==r.releasedAt))throw new Error("invalid MCP active desired-state receipt");const a={...n,...o?{mcpDesiredStateCommit:o}:{},...r?{mcpLiveRelease:r}:{},...i?{mcpActiveDesiredState:i}:{}};if(!a.projectionReceipt)return a;const s=a.projectionReceipt,c=["revision","state",...void 0===s.snapshot?.platformAgentId?[]:["platformAgentId"],...void 0===s.snapshot?.mcpReceipt?[]:["mcpReceipt"]].sort().join(",");if("revision,schemaVersion,snapshot,snapshotHash"!==Object.keys(s).sort().join(",")||Object.keys(s.snapshot??{}).sort().join(",")!==c||"projection_committed"!==a.phase||1!==s.schemaVersion||!/^(0|[1-9][0-9]*)$/.test(s.revision)||"synced"!==s.snapshot?.state||s.snapshot.revision!==s.revision)throw new Error("invalid projection receipt");const d=projectionReceipt(s.snapshot),m=d.snapshot.mcpReceipt;if(m&&(m.operationId!==a.operationId||m.method!==a.command||m.runtimeEpoch!==a.runtimeEpoch))throw new Error("MCP projection receipt does not bind journal operation");if(d.snapshotHash!==s.snapshotHash)throw new Error("invalid projection receipt hash");return{...a,projectionReceipt:d}}function normalizeMcpDesiredStateCommit(e){if(!e||"object"!=typeof e||"desiredStateHash,desiredStateRevision,method,operationId,paramsHash,runtimeEpoch,schemaVersion,server,source"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||"service"!==e.source&&"acp_local"!==e.source||!safeReceiptIdentifier(e.operationId)||"mcp.set"!==e.method&&"mcp.unset"!==e.method||!isSha256(e.paramsHash)||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||!/^(0|[1-9][0-9]*)$/.test(e.desiredStateRevision)||!isSha256(e.desiredStateHash))throw new Error("invalid MCP desired-state commit");return Object.freeze({schemaVersion:1,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:normalizeMcpReleaseServer(e.server)})}function normalizeMcpLiveRelease(e){if(!e||"object"!=typeof e)throw new Error("invalid MCP live release receipt");const t=["desiredStateHash","desiredStateRevision","method","operationId","paramsHash","releasedAt","runtimeEpoch","schemaVersion","server","source",..."service"===e.source?["effectRecordHash"]:[]].sort().join(",");if(Object.keys(e).sort().join(",")!==t||"service"===e.source&&!isSha256(e.effectRecordHash)||"acp_local"===e.source&&void 0!==e.effectRecordHash||"string"!=typeof e.releasedAt||!Number.isFinite(Date.parse(e.releasedAt)))throw new Error("invalid MCP live release receipt");const n=normalizeMcpDesiredStateCommit({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server});return Object.freeze({...n,...e.effectRecordHash?{effectRecordHash:e.effectRecordHash}:{},releasedAt:e.releasedAt})}function normalizeMcpActiveDesiredState(e){if(!e||"object"!=typeof e)throw new Error("invalid MCP active desired-state receipt");const t=["activatedAt","desiredStateHash","desiredStateRevision","method","operationId","paramsHash","releasedAt","runtimeEpoch","schemaVersion","server","source",..."service"===e.source?["effectRecordHash","terminalMessageId"]:[]].sort().join(",");if(Object.keys(e).sort().join(",")!==t||"string"!=typeof e.activatedAt||!Number.isFinite(Date.parse(e.activatedAt))||"service"===e.source&&!safeReceiptIdentifier(e.terminalMessageId))throw new Error("invalid MCP active desired-state receipt");const n=normalizeMcpLiveRelease({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server,releasedAt:e.releasedAt,...e.effectRecordHash?{effectRecordHash:e.effectRecordHash}:{}});return Object.freeze({...n,...e.terminalMessageId?{terminalMessageId:e.terminalMessageId}:{},activatedAt:e.activatedAt})}function normalizeMcpReleaseServer(e){const t=e&&"object"==typeof e?Object.keys(e).sort().join(","):"",n=!0===e?.removed;if(!(t===(n?"name,removed":void 0===e?.credentialRef?"definitionRef,definitionSha256,name,version":"credentialRef,definitionRef,definitionSha256,name,version")&&safeReceiptIdentifier(e?.name)&&(n||safeMcpReference(e?.definitionRef)&&safeMcpReference(e?.version)&&isSha256(e?.definitionSha256)&&(void 0===e?.credentialRef||safeMcpReference(e.credentialRef)))))throw new Error("invalid MCP desired-state server identity");return Object.freeze(n?{name:e.name,removed:!0}:{name:e.name,definitionRef:e.definitionRef,version:e.version,definitionSha256:e.definitionSha256,...e.credentialRef?{credentialRef:e.credentialRef}:{}})}function assertMcpCommitBindsRecord(e,t){if(e.operationId!==t.operationId||e.method!==t.command||e.paramsHash!==t.paramsHash||e.runtimeEpoch!==t.runtimeEpoch)throw new RuntimeCommandJournalConflictError("MCP desired-state commit does not bind command journal")}function releaseFromCommit(e,t,n){if(t.operationId!==e.operationId||t.source!==e.source||"service"===t.source&&!isSha256(t.effectRecordHash)||"acp_local"===t.source&&void 0!==t.effectRecordHash)throw new RuntimeCommandJournalConflictError("MCP live release proof does not bind desired state");return Object.freeze({...e,...t.effectRecordHash?{effectRecordHash:t.effectRecordHash}:{},releasedAt:n})}function activeFromRelease(e,t,n){if(!(e&&t.operationId===e.operationId&&t.source===e.source&&Number.isFinite(Date.parse(n))&&("service"!==t.source||isSha256(t.effectRecordHash)&&t.effectRecordHash===e.effectRecordHash&&safeReceiptIdentifier(t.terminalMessageId))&&("acp_local"!==t.source||void 0===t.effectRecordHash&&void 0===t.terminalMessageId)))throw new RuntimeCommandJournalConflictError("MCP active promotion does not bind terminal release proof");return Object.freeze({...e,...t.terminalMessageId?{terminalMessageId:t.terminalMessageId}:{},activatedAt:n})}function sameMcpDesiredStateCommit(e,t){if(!e||!t)return!1;const n=e=>({schemaVersion:e.schemaVersion,source:e.source,operationId:e.operationId,method:e.method,paramsHash:e.paramsHash,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:e.server});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function sameMcpLiveRelease(e,t){const n=e=>({...e,releasedAt:""});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function sameMcpActiveDesiredState(e,t){const n=e=>({...e,releasedAt:"",activatedAt:""});return canonicalizeJcs(n(e))===canonicalizeJcs(n(t))}function normalizeMcpProjectionReceipt(e){if(!e||"object"!=typeof e||"desiredStateHash,desiredStateRevision,method,operationId,runtimeEpoch,schemaVersion,server"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||!safeReceiptIdentifier(e.operationId)||"mcp.set"!==e.method&&"mcp.unset"!==e.method||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||!/^(0|[1-9][0-9]*)$/.test(e.desiredStateRevision)||!isSha256(e.desiredStateHash))throw new Error("invalid MCP projection receipt");const t=e.server;if((t&&"object"==typeof t?Object.keys(t).sort().join(","):"")!==(!0===t?.removed?"name,removed":void 0===t?.definitionRef?"name":"definitionRef,definitionSha256,name")||!safeReceiptIdentifier(t?.name)||void 0!==t?.definitionRef&&(!safeReceiptIdentifier(t.definitionRef)||!isSha256(t.definitionSha256)))throw new Error("invalid MCP projection server receipt");return Object.freeze({schemaVersion:1,operationId:e.operationId,method:e.method,runtimeEpoch:e.runtimeEpoch,desiredStateRevision:e.desiredStateRevision,desiredStateHash:e.desiredStateHash,server:Object.freeze(!0===t.removed?{name:t.name,removed:!0}:void 0===t.definitionRef?{name:t.name}:{name:t.name,definitionRef:t.definitionRef,definitionSha256:t.definitionSha256})})}function isSha256(e){return"string"==typeof e&&/^[a-f0-9]{64}$/.test(e)}function safeReceiptIdentifier(e){return"string"==typeof e&&/^[a-zA-Z0-9._:@/-]{1,512}$/.test(e)&&!/(authorization|bearer|credential|secret|token|password|private(?:_|-)?key|headers?|env)/i.test(e)}function safeMcpReference(e){return"string"==typeof e&&/^[a-zA-Z0-9._:@/-]{1,512}$/.test(e)&&!/(authorization|bearer|secret|token|password|private(?:_|-)?key|headers?|env)/i.test(e)}function agentProjectionTerminalReceipt(e){if(!/^(0|[1-9][0-9]*)$/.test(e))throw new Error("invalid agent projection terminal revision");return Object.freeze({schemaVersion:1,state:"blocked",revision:e})}function normalizeAgentProjectionTerminal(e){if("revision,schemaVersion,state"!==Object.keys(e).sort().join(",")||1!==e.schemaVersion||"blocked"!==e.state)throw new Error("invalid agent projection terminal receipt");return agentProjectionTerminalReceipt(e.revision)}function normalizeAgentProjectionIntent(e,t){const n=Object.keys(e).sort(),o=["canonicalHash","coreResultHash","method","operationId","projectionRevision","runtimeAgentUid","runtimeEpoch","runtimeLocalKey","schemaVersion","snapshotRef",...void 0===e.expectedPlatformAgentId?[]:["expectedPlatformAgentId"],...void 0===e.removedBindings?[]:["removedBindings"]].sort();if(n.length!==o.length||n.some((e,t)=>e!==o[t])||1!==e.schemaVersion||!["agent.create","agent.update","agent.delete","agent.applyTemplate"].includes(e.method)||!nonEmptyText(e.operationId)||!nonEmptyText(e.runtimeAgentUid)||!nonEmptyText(e.runtimeLocalKey)||!/^(0|[1-9][0-9]*)$/.test(e.runtimeEpoch)||e.operationId!==t.operationId||e.method!==t.command||e.runtimeEpoch!==t.runtimeEpoch||void 0!==e.expectedPlatformAgentId&&!nonEmptyText(e.expectedPlatformAgentId)||!/^[0-9a-f]{64}$/.test(e.coreResultHash)||!/^(0|[1-9][0-9]*)$/.test(e.projectionRevision)||!/^[0-9a-f]{64}$/.test(e.canonicalHash)||!nonEmptyText(e.snapshotRef)||e.snapshotRef!==projectionSnapshotRef(e.runtimeEpoch,e.projectionRevision,e.canonicalHash)||void 0!==e.removedBindings&&(!Number.isSafeInteger(e.removedBindings)||e.removedBindings<0))throw new Error("invalid agent projection intent");return Object.freeze({...e})}function normalizeAgentProjectionPreparation(e,t){if(isAgentProjectionIntent(e))return normalizeAgentProjectionIntent(e,t);const n=e,o=Object.keys(n).sort(),r=["coreResultHash","errorKind","method","operationId","runtimeAgentUid","runtimeEpoch","runtimeLocalKey","schemaVersion","state",...void 0===n.expectedPlatformAgentId?[]:["expectedPlatformAgentId"],...void 0===n.removedBindings?[]:["removedBindings"]].sort();if(o.length!==r.length||o.some((e,t)=>e!==r[t])||1!==n.schemaVersion||!["agent.create","agent.update","agent.delete","agent.applyTemplate"].includes(n.method)||n.operationId!==t.operationId||n.method!==t.command||n.runtimeEpoch!==t.runtimeEpoch||!nonEmptyText(n.runtimeAgentUid)||!nonEmptyText(n.runtimeLocalKey)||!/^(0|[1-9][0-9]*)$/.test(n.runtimeEpoch)||!/^[0-9a-f]{64}$/.test(n.coreResultHash)||"pending"!==n.state&&"blocked"!==n.state||"snapshot_retryable"!==n.errorKind&&"snapshot_nonretryable"!==n.errorKind||"pending"===n.state!=("snapshot_retryable"===n.errorKind)||void 0!==n.expectedPlatformAgentId&&!nonEmptyText(n.expectedPlatformAgentId)||void 0!==n.removedBindings&&(!Number.isSafeInteger(n.removedBindings)||n.removedBindings<0))throw new Error("invalid agent projection preparation failure");return Object.freeze({...n})}function sameProjectionPreparationBase(e,t){return e.operationId===t.operationId&&e.method===t.method&&e.runtimeEpoch===t.runtimeEpoch&&e.runtimeAgentUid===t.runtimeAgentUid&&e.runtimeLocalKey===t.runtimeLocalKey&&e.coreResultHash===t.coreResultHash&&e.expectedPlatformAgentId===t.expectedPlatformAgentId&&e.removedBindings===t.removedBindings}function isAgentProjectionIntent(e){return"projectionRevision"in e}function projectionSnapshotRef(e,t,n){return`runtime-agent-projection:v1:${e}:${t}:${n}`}function nonEmptyText(e){return"string"==typeof e&&e.length>0}
@@ -1 +1 @@
1
- import{RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{parseAndValidateTemplatePackage}from"../acp-gateway/agent-template-package.js";import{HttpRuntimeOperationResourceResolver,RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{MessageServiceRuntimeMessageChannel}from"./message-service-runtime-message-channel.js";import{MessageServiceConversationClearHttpAuthority}from"./message-service-conversation-clear-http-authority.js";import{MessageServiceConversationHistoryBoundary}from"./message-service-conversation-history-boundary.js";import{MessageServiceSessionModelHttpAuthority}from"./message-service-session-model-http-authority.js";import{MsRuntimeMessageChannelConsumerFactory}from"./ms-runtime-message-channel-consumer-factory.js";import{RuntimeJwksVerifier}from"./runtime-jwks-verifier.js";import{GatewayGrantJwksVerifier}from"./split-runtime-authorization-verifier.js";import{RUNTIME_MESSAGE_CHANNEL_REVISION,RUNTIME_PARITY_MANIFEST}from"./runtime-parity-manifest.js";import{intersectRuntimeServiceMethodsWithBackendCapabilities,loadRuntimeServiceContractIdentity,runtimeServiceContractRequirement}from"./runtime-service-contract-compatibility.js";const CONTRACT_ARTIFACT_BLOCKER=Object.freeze({id:"contract_artifact_mismatch",owner:"backend",requiredContract:"frozen Types manifest and Service wire artifacts"}),TRANSPORT_BLOCKER=Object.freeze({id:"message_channel_transport",owner:"backend",requiredContract:`${RUNTIME_MESSAGE_CHANNEL_REVISION} HTTP delivery/history/append authority`}),JWKS_BLOCKER=Object.freeze({id:"gateway_grant_jwks",owner:"backend",requiredContract:"Purpose-bound Gateway runtime execution-grant JWKS proxy"}),POISON_JOURNAL_BLOCKER=Object.freeze({id:"poison_journal",owner:"openclaw_runtime_client",requiredContract:"Durable bounded runtime poison-delivery journal"}),grantAdapterOptions=new WeakMap;export class AgentGatewayGrantJwksReadinessAdapter{constructor(e){grantAdapterOptions.set(this,{agentGatewayUrl:new URL(e.agentGatewayUrl).toString()})}}export class AgentGatewaySplitJwksReadinessAdapter extends AgentGatewayGrantJwksReadinessAdapter{}const transportOptions=new WeakMap;export class RuntimeServiceTransportReadinessAdapter{constructor(e){transportOptions.set(this,{messageService:e.messageService,scopedDeliveryKey:e.scopedDeliveryKey,agentGatewayUrl:new URL(e.agentGatewayUrl).toString(),boundAgentFetch:e.boundAgentFetch})}}const CAPABILITY_BLOCKERS=Object.freeze(RUNTIME_PARITY_MANIFEST.filter(e=>"blocked"===e.status).map(e=>Object.freeze({method:e.method,owner:e.blockerOwner,requiredContract:e.contractRevision})));export async function createRuntimeServiceProductionCandidate(e){try{loadRuntimeServiceContractIdentity()}catch{return{status:"unavailable",blockers:Object.freeze([{...CONTRACT_ARTIFACT_BLOCKER,requiredContract:contractArtifactRequirement()}])}}if(!e.poisonJournal)return{status:"unavailable",blockers:Object.freeze([POISON_JOURNAL_BLOCKER])};let t;if(e.authorizationAdapter)try{t=await probeGatewayGrantJwks(e.authorizationAdapter)}catch{}const r=e.transportAdapter?transportOptions.get(e.transportAdapter):void 0;if(!r)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!t)return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};try{const n=await r.messageService.ready(),a=new MessageServiceRuntimeMessageChannel({serviceUrl:n.serviceUrl,transport:r.messageService,scopedDeliveryKey:r.scopedDeliveryKey}),i=runtimeMethods(),s=e.attemptPorts??runtimeMessageAttemptPorts(r,n.serviceUrl);return{status:"ready",blockers:[],capabilityBlockers:CAPABILITY_BLOCKERS,serviceMethods:i,factory:new MsRuntimeMessageChannelConsumerFactory({channel:a,verifier:t,ports:s,journal:e.commandJournal,allowedMethods:new Set(i),options:{poisonJournal:e.poisonJournal,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{}}}),...void 0===e.attemptPorts?{resourceBindingContractRevision:RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}:{}}}catch{return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER])}}}async function probeGatewayGrantJwks(e){const t=grantAdapterOptions.get(e);if(!t)throw new Error("untrusted Gateway grant readiness adapter");const r=t.agentGatewayUrl.replace(/\/+$/,""),n=new GatewayGrantJwksVerifier(new RuntimeJwksVerifier(`${r}/.well-known/runtime-execution-grant-jwks.json`));return await n.refresh(!0),n}export function runtimeMessageAttemptPorts(e,t){const r=new MessageServiceConversationClearHttpAuthority({messageServiceUrl:t,fetchAuthorized:async(t,r)=>await e.messageService.fetch(t,r)});return{async resolve({request:n,command:a,executionGrant:i,getLease:s,journalStoreId:o,journalGeneration:c,journalRecord:u,abortSignal:d}){const m="skills.install"===a.name||"agent.applyTemplate"===a.name?a.params.artifact:void 0,l="mcp.set"===a.name?a.params.definition:void 0;if((m||l)&&(n.resourceBindingContractRevision!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||!n.resourceBindings))throw new RuntimeResourceResolutionError("resolution_rejected","resource-bound Service delivery requires the Types 4.1 binding carrier");const p=new HttpRuntimeOperationResourceResolver({gatewayUrl:e.agentGatewayUrl,operationId:n.operationId,requestMessageId:n.requestMessageId,instanceId:n.instanceId,paramsHash:n.paramsHash,bindings:n.resourceBindings??[],getAuthorization:()=>{const e=s();return{executionGrant:i,runtimeLeaseCredential:e.runtimeLeaseCredential,fence:deliveryFence(n,e,o,c)}},fetchAuthorized:async(t,r)=>await e.boundAgentFetch(t,r??{}),parseSkillArtifact:e=>parseAndValidateTemplatePackage(Buffer.from(e)),signal:d});if("prepared"!==u.phase&&"started"!==u.phase||(m&&await p.readVerifiedArtifact(m),l&&await p.resolveMcpDefinition(l)),"session.setModel"===a.name)return{resources:p,sessionModelProjection:{project:async({conversationId:r,modelOverrideId:a})=>{const i=s(),o=new MessageServiceSessionModelHttpAuthority({messageServiceUrl:t,fetchAuthorized:async(t,r)=>await e.messageService.fetchRuntimeChannel(t,{runtimeLeaseCredential:i.runtimeLeaseCredential,scopedDeliveryKey:e.scopedDeliveryKey},r)});return await o.project({conversationId:r,operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,modelOverrideId:a,runtimeLeaseCredential:i.runtimeLeaseCredential})}}};if("session.clear"!==a.name)return{resources:p};const R=a.context.conversation,y=s();return{resources:p,historyBoundary:new MessageServiceConversationHistoryBoundary({operationId:n.operationId,requestMessageId:n.requestMessageId,expectedGeneration:await r.currentGeneration(R.runtimeConversationUid),fence:deliveryFence(n,y,o,c),authority:r})}}}}function runtimeMethods(){return[...intersectRuntimeServiceMethodsWithBackendCapabilities(RUNTIME_PARITY_MANIFEST.filter(e=>"ready"===e.status).map(e=>e.method))]}function contractArtifactRequirement(){try{return runtimeServiceContractRequirement()}catch{return CONTRACT_ARTIFACT_BLOCKER.requiredContract}}function deliveryFence(e,t,r,n){if(t.handlerIdentity!==e.runtimeHandlerIdentity||t.runtimeEpoch!==e.dispatchEpoch)throw new Error("runtime delivery fence does not match the retained request");return{claimId:e.requestMessageId,handlerIdentity:t.handlerIdentity,runtimeEpoch:t.runtimeEpoch,fencingToken:t.runtimeEpoch,journalStoreId:r,journalGeneration:n}}
1
+ import{RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{parseAndValidateTemplatePackage}from"../acp-gateway/agent-template-package.js";import{HttpRuntimeOperationResourceResolver,RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{MessageServiceRuntimeMessageChannel}from"./message-service-runtime-message-channel.js";import{MessageServiceConversationClearHttpAuthority}from"./message-service-conversation-clear-http-authority.js";import{MessageServiceConversationHistoryBoundary}from"./message-service-conversation-history-boundary.js";import{MessageServiceSessionModelHttpAuthority}from"./message-service-session-model-http-authority.js";import{MsRuntimeMessageChannelConsumerFactory}from"./ms-runtime-message-channel-consumer-factory.js";import{RuntimeJwksVerifier}from"./runtime-jwks-verifier.js";import{GatewayGrantJwksVerifier}from"./split-runtime-authorization-verifier.js";import{RUNTIME_MESSAGE_CHANNEL_REVISION,RUNTIME_PARITY_MANIFEST}from"./runtime-parity-manifest.js";import{intersectRuntimeServiceMethodsWithBackendCapabilities,loadRuntimeServiceContractIdentity,runtimeServiceContractRequirement}from"./runtime-service-contract-compatibility.js";const CONTRACT_ARTIFACT_BLOCKER=Object.freeze({id:"contract_artifact_mismatch",owner:"backend",requiredContract:"frozen Types manifest and Service wire artifacts"}),TRANSPORT_BLOCKER=Object.freeze({id:"message_channel_transport",owner:"backend",requiredContract:`${RUNTIME_MESSAGE_CHANNEL_REVISION} HTTP delivery/history/append authority`}),JWKS_BLOCKER=Object.freeze({id:"gateway_grant_jwks",owner:"backend",requiredContract:"Purpose-bound Gateway runtime execution-grant JWKS proxy"}),POISON_JOURNAL_BLOCKER=Object.freeze({id:"poison_journal",owner:"openclaw_runtime_client",requiredContract:"Durable bounded runtime poison-delivery journal"}),grantAdapterOptions=new WeakMap;export class AgentGatewayGrantJwksReadinessAdapter{constructor(e){grantAdapterOptions.set(this,{agentGatewayUrl:new URL(e.agentGatewayUrl).toString()})}}export class AgentGatewaySplitJwksReadinessAdapter extends AgentGatewayGrantJwksReadinessAdapter{}const transportOptions=new WeakMap;export class RuntimeServiceTransportReadinessAdapter{constructor(e){transportOptions.set(this,{messageService:e.messageService,scopedDeliveryKey:e.scopedDeliveryKey,agentGatewayUrl:new URL(e.agentGatewayUrl).toString(),boundAgentFetch:e.boundAgentFetch})}}const CAPABILITY_BLOCKERS=Object.freeze(RUNTIME_PARITY_MANIFEST.filter(e=>"blocked"===e.status).map(e=>Object.freeze({method:e.method,owner:e.blockerOwner,requiredContract:e.contractRevision})));export async function createRuntimeServiceProductionCandidate(e){try{loadRuntimeServiceContractIdentity()}catch{return{status:"unavailable",blockers:Object.freeze([{...CONTRACT_ARTIFACT_BLOCKER,requiredContract:contractArtifactRequirement()}])}}if(!e.poisonJournal)return{status:"unavailable",blockers:Object.freeze([POISON_JOURNAL_BLOCKER])};let t;if(e.authorizationAdapter)try{t=await probeGatewayGrantJwks(e.authorizationAdapter)}catch{}const r=e.transportAdapter?transportOptions.get(e.transportAdapter):void 0;if(!r)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!t)return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};try{const n=await r.messageService.ready(),a=new MessageServiceRuntimeMessageChannel({serviceUrl:n.serviceUrl,transport:r.messageService,scopedDeliveryKey:r.scopedDeliveryKey}),i=runtimeMethods(),s=e.attemptPorts??runtimeMessageAttemptPorts(r,n.serviceUrl);return{status:"ready",blockers:[],capabilityBlockers:CAPABILITY_BLOCKERS,serviceMethods:i,factory:new MsRuntimeMessageChannelConsumerFactory({channel:a,verifier:t,ports:s,journal:e.commandJournal,allowedMethods:new Set(i),options:{poisonJournal:e.poisonJournal,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{},...e.onConsumerRequestFailure?{onRequestFailure:e.onConsumerRequestFailure}:{}}}),...void 0===e.attemptPorts?{resourceBindingContractRevision:RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}:{}}}catch{return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER])}}}async function probeGatewayGrantJwks(e){const t=grantAdapterOptions.get(e);if(!t)throw new Error("untrusted Gateway grant readiness adapter");const r=t.agentGatewayUrl.replace(/\/+$/,""),n=new GatewayGrantJwksVerifier(new RuntimeJwksVerifier(`${r}/.well-known/runtime-execution-grant-jwks.json`));return await n.refresh(!0),n}export function runtimeMessageAttemptPorts(e,t){const r=new MessageServiceConversationClearHttpAuthority({messageServiceUrl:t,fetchAuthorized:async(t,r)=>await e.messageService.fetch(t,r)});return{async resolve({request:n,command:a,executionGrant:i,getLease:s,journalStoreId:o,journalGeneration:c,journalRecord:u,abortSignal:d}){const l="skills.install"===a.name||"agent.applyTemplate"===a.name?a.params.artifact:void 0,m="mcp.set"===a.name?a.params.definition:void 0;if((l||m)&&(n.resourceBindingContractRevision!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||!n.resourceBindings))throw new RuntimeResourceResolutionError("resolution_rejected","resource-bound Service delivery requires the Types 4.1 binding carrier");const p=new HttpRuntimeOperationResourceResolver({gatewayUrl:e.agentGatewayUrl,operationId:n.operationId,requestMessageId:n.requestMessageId,instanceId:n.instanceId,paramsHash:n.paramsHash,bindings:n.resourceBindings??[],getAuthorization:()=>{const e=s();return{executionGrant:i,runtimeLeaseCredential:e.runtimeLeaseCredential,fence:deliveryFence(n,e,o,c)}},fetchAuthorized:async(t,r)=>await e.boundAgentFetch(t,r??{}),parseSkillArtifact:e=>parseAndValidateTemplatePackage(Buffer.from(e)),signal:d});if("prepared"!==u.phase&&"started"!==u.phase||(l&&await p.readVerifiedArtifact(l),m&&await p.resolveMcpDefinition(m)),"session.setModel"===a.name)return{resources:p,sessionModelProjection:{project:async({conversationId:r,modelOverrideId:a})=>{const i=s(),o=new MessageServiceSessionModelHttpAuthority({messageServiceUrl:t,fetchAuthorized:async(t,r)=>await e.messageService.fetchRuntimeChannel(t,{runtimeLeaseCredential:i.runtimeLeaseCredential,scopedDeliveryKey:e.scopedDeliveryKey},r)});return await o.project({conversationId:r,operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,modelOverrideId:a,runtimeLeaseCredential:i.runtimeLeaseCredential})}}};if("session.clear"!==a.name)return{resources:p};const R=a.context.conversation,y=s();return{resources:p,historyBoundary:new MessageServiceConversationHistoryBoundary({operationId:n.operationId,requestMessageId:n.requestMessageId,expectedGeneration:await r.currentGeneration(R.runtimeConversationUid),fence:deliveryFence(n,y,o,c),authority:r})}}}}function runtimeMethods(){return[...intersectRuntimeServiceMethodsWithBackendCapabilities(RUNTIME_PARITY_MANIFEST.filter(e=>"ready"===e.status).map(e=>e.method))]}function contractArtifactRequirement(){try{return runtimeServiceContractRequirement()}catch{return CONTRACT_ARTIFACT_BLOCKER.requiredContract}}function deliveryFence(e,t,r,n){if(t.handlerIdentity!==e.runtimeHandlerIdentity||t.runtimeEpoch!==e.dispatchEpoch)throw new Error("runtime delivery fence does not match the retained request");return{claimId:e.requestMessageId,handlerIdentity:t.handlerIdentity,runtimeEpoch:t.runtimeEpoch,fencingToken:t.runtimeEpoch,journalStoreId:r,journalGeneration:n}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.5.98",
3
+ "version": "0.6.0",
4
4
  "description": "OpenClaw plugin that bridges the BeeOS platform with the local OpenClaw Gateway, providing file transfer, ACP bridge, and observability.",
5
5
  "license": "MIT",
6
6
  "author": "BeeOS <dev@beeos.ai>",