beeos-claw 0.5.65-staging.1 → 0.5.65-staging.3

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.
@@ -1 +1 @@
1
- import{WebSocket}from"ws";import{EventEmitter}from"node:events";import{randomUUID,createHash}from"node:crypto";import{loadOrCreateDeviceIdentity,buildDeviceAuthPayload,signDeviceAuth,ensureDevicePaired,warmupCryptoSign}from"./device-identity.js";const MIN_PROTOCOL_VERSION=3,MAX_PROTOCOL_VERSION=4,CONNECT_TIMEOUT_MS=3e4,HEARTBEAT_INTERVAL_MS=15e3,LIVENESS_TIMEOUT_MS=6e4,CHAT_HISTORY_PAGE_TIMEOUT_MS=1e4,AUTH_FAILURE_CLOSE_CODE=4001,EL_LAG_INTERVAL_MS=200,EL_LAG_THRESHOLD_MS=500,RECONNECT_NOTIFY_MIN_MS=6e4,RECONNECT_NOTIFY_MAX_MS=18e4,RECONNECT_NOTIFY_METHOD="server/reconnect";export function decodeChatAbortResult(e){if("object"!=typeof e||null===e||Array.isArray(e))throw new Error("Invalid chat.abort response: expected object");const t=e;if("boolean"!=typeof t.aborted)throw new Error("Invalid chat.abort response: expected boolean aborted");if(!Array.isArray(t.runIds)||t.runIds.some(e=>"string"!=typeof e))throw new Error("Invalid chat.abort response: expected string[] runIds");return{aborted:t.aborted,runIds:[...t.runIds]}}function wsUrlToHttpBase(e){try{const t=new URL(e),n="wss:"===t.protocol?"https:":"ws:"===t.protocol?"http:":null;return n?`${n}//${t.host}`:null}catch{return null}}const MANUAL_RECONNECT_SIGNAL="SIGUSR1";function registerReconnectSignal(e,t,n){detachReconnectSignal(e),e.handler=t;try{process.on("SIGUSR1",t),n?.info?.("[gateway] manual reconnect enabled signal=SIGUSR1")}catch{n?.warn?.("[gateway] manual reconnect signal not supported")}}function detachReconnectSignal(e){if(e.handler){try{process.off("SIGUSR1",e.handler)}catch{}e.handler=null}}export class GatewayClient extends EventEmitter{ws=null;config;pending=new Map;connected=!1;stopped=!1;authFailed=!1;connectPromise=null;reconnectTimer=null;reconnectAttempt=0;nextReconnectDelayHintMs=null;signalState={handler:null};deviceIdentity;heartbeatTimer=null;lastPongTs=0;_connectionSeq=0;_currentConnectionId=null;_lastReadyAt=0;reconnectNotifyTimer=null;constructor(e){super(),this.config=e,this.deviceIdentity=loadOrCreateDeviceIdentity();try{ensureDevicePaired(this.deviceIdentity)}catch(e){}Date.now();warmupCryptoSign(this.deviceIdentity)}async start(){this.stopped=!1,this.reconnectAttempt=0,registerReconnectSignal(this.signalState,()=>{this.stopped||(this.emit("manual-reconnect"),this.reconnectNow())}),await this.connect()}stop(){this.stopped=!0,this.stopHeartbeat(),this.clearReconnectNotify(),detachReconnectSignal(this.signalState),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.close()}async connect(){if(this.connectPromise)return this.connectPromise;this.connectPromise=this._connect();try{await this.connectPromise}finally{this.connectPromise=null}}_connect(){return new Promise((e,t)=>{if(this.authFailed)return void t(new Error("Authentication failed — not retrying"));const n=Date.now();this.emit("transport",{event:"connect_start",url:this.config.url,attempt:this.reconnectAttempt});const s=new WebSocket(this.config.url);this.ws=s;let i=null;const o=setTimeout(()=>{this.emit("transport",{event:"connect_fail",reason:"timeout",durationMs:Date.now()-n}),s.close(),t(new Error("Gateway connection timeout"))},3e4),r=t=>{clearTimeout(o),this.connected=!0,this.reconnectAttempt=0,this.authFailed=!1,this._connectionSeq++,this._currentConnectionId=randomUUID(),this._lastReadyAt=Date.now(),this.startHeartbeat(),this.emit("transport",{event:"connect_ok",connectionSeq:this._connectionSeq,connectionId:this._currentConnectionId,durationMs:Date.now()-n}),this.emit("connected",{...t,connectionSeq:this._connectionSeq,connectionId:this._currentConnectionId}),e()},c=e=>{clearTimeout(o),this.authFailed=!0,this.emit("transport",{event:"connect_fail",reason:e,durationMs:Date.now()-n}),this.emit("auth-failed",{reason:e}),s.close(),t(new Error(e))};s.on("open",()=>{this.emit("transport",{event:"ws_open",durationMs:Date.now()-n})}),s.on("message",e=>{const a="string"==typeof e?e:e.toString("utf-8");if("pong"===a)return void(this.lastPongTs=Date.now());let h;this.emitMessageHash(a);try{h=JSON.parse(a)}catch{return}const l=h.method;l&&!h.id&&(this.isReconnectNotification(l)?this.scheduleReconnectNotify():this.reconnectNotifyTimer&&this.clearReconnectNotify(),this.emit("notification",l,h.params));const d=h.type;if("event"===d&&this.reconnectNotifyTimer&&this.clearReconnectNotify(),"event"===d){const e=h.event,t=h.payload;if("connect.challenge"===e){const e=Date.now();this.emit("transport",{event:"handshake.challenge_recv",durationMs:e-n});const s=t?.nonce;i=this._sendConnect(s),this.emit("transport",{event:"handshake.connect_sent",durationMs:Date.now()-n});let o=0;const r=setInterval(()=>{o++},200),c=Date.now(),a=setInterval(()=>{const e=Date.now()-c,t=e-200*o;t>500&&this.emit("transport",{event:"eventloop_lag",lagMs:t,elapsedMs:e,probeSeq:o})},200),h=()=>{clearInterval(r),clearInterval(a),this.off("connected",h),this.off("disconnected",h)};setTimeout(h,3e4),this.once("connected",h),this.once("disconnected",h)}else"chat"===e?this.emit("chat",t):"agent"===e?this.emit("agent",t):"cron"===e?this.emit("cron",t):"canvas"===e?this.emit("canvas",t):"session.message"===e?this.emit("sessionMessage",t):this.emit("gateway-event",e,t)}else if("hello-ok"===d)this.emit("transport",{event:"handshake.hello_ok_recv",durationMs:Date.now()-n}),r(h);else if("hello-error"===d||"connect-error"===d)c("Authentication rejected by Gateway");else if("res"===d){const e=h.id;if(e&&e===i){if(this.emit("transport",{event:"handshake.connect_res_recv",ok:!!h.ok,durationMs:Date.now()-n}),h.ok){const e=h.payload;r(e??h)}else{const e=h.error;!0===e?.retryable||"UNAVAILABLE"===e?.code?(clearTimeout(o),this.emit("transport",{event:"connect_fail",reason:e?.message||"Gateway unavailable; will retry",retryable:!0,retryAfterMs:e?.retryAfterMs,durationMs:Date.now()-n}),"number"==typeof e?.retryAfterMs&&e.retryAfterMs>0&&(this.nextReconnectDelayHintMs=e.retryAfterMs),s.close(),t(new Error(e?.message||"Gateway unavailable"))):c(e?.message||"Gateway connect rejected")}return}const a=this.pending.get(e);if(a)if(this.pending.delete(e),clearTimeout(a.timer),h.ok)a.resolve(h.payload);else{const e=h.error;a.reject(new Error(e?.message||"Gateway request failed"))}else this.emit("response",h)}}),s.on("pong",()=>{this.lastPongTs=Date.now()}),s.on("close",e=>{const s=this.connected;this.stopHeartbeat(),this.clearReconnectNotify(),this.emit("transport",{event:"close",code:e,durationMs:Date.now()-n}),this.connected=!1,this.connectPromise=null;for(const[,e]of this.pending)clearTimeout(e.timer),e.reject(new Error("Connection closed"));this.pending.clear(),s||(clearTimeout(o),t(new Error(`Connection closed during handshake (code=${e})`))),4001===e&&(this.authFailed=!0,this.emit("auth-failed",{code:e})),this.emit("disconnected"),this.stopped||this.authFailed||this.scheduleReconnect()}),s.on("error",e=>{this.emit("transport",{event:"error",message:e.message,durationMs:Date.now()-n}),this.stopped?clearTimeout(o):(this.connected||(clearTimeout(o),t(e)),this.emit("error",e))})})}emitMessageHash(e){if(e.length<32)return;const t=createHash("sha256").update(e).digest("hex").slice(0,16);this.emit("transport",{event:"message_recv",hash:t,size:e.length})}startHeartbeat(){this.stopHeartbeat(),this.lastPongTs=Date.now(),this.heartbeatTimer=setInterval(()=>{if(!this.ws||!this.connected)return;try{this.ws.ping()}catch{}const e=Date.now()-this.lastPongTs;e>6e4&&(this.emit("liveness-timeout",{elapsed:e}),this.ws.close())},15e3),"object"==typeof this.heartbeatTimer&&"unref"in this.heartbeatTimer&&this.heartbeatTimer.unref()}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}isReconnectNotification(e){return"server/reconnect"===e||e.endsWith("/reconnect")}scheduleReconnectNotify(){this.clearReconnectNotify();const e=6e4+12e4*Math.random();this.emit("reconnect-notify-scheduled",{delayMs:Math.round(e)}),this.reconnectNotifyTimer=setTimeout(()=>{this.reconnectNotifyTimer=null,!this.stopped&&this.connected&&(this.emit("reconnect-notify-fired"),this.reconnectNow())},e),"object"==typeof this.reconnectNotifyTimer&&"unref"in this.reconnectNotifyTimer&&this.reconnectNotifyTimer.unref()}clearReconnectNotify(){this.reconnectNotifyTimer&&(clearTimeout(this.reconnectNotifyTimer),this.reconnectNotifyTimer=null)}scheduleReconnect(){if(this.stopped)return;if(this.reconnectTimer)return;const e=this.config.retry??{baseMs:1e3,maxMs:6e5,maxAttempts:0};if(e.maxAttempts>0&&this.reconnectAttempt>=e.maxAttempts)return void this.emit("reconnect-exhausted",this.reconnectAttempt);let t;null!==this.nextReconnectDelayHintMs&&this.nextReconnectDelayHintMs>0?(t=Math.min(this.nextReconnectDelayHintMs,e.maxMs),this.nextReconnectDelayHintMs=null):t=Math.min(e.baseMs*Math.pow(2,this.reconnectAttempt)+500*Math.random(),e.maxMs),this.reconnectAttempt++,this.emit("reconnecting",{attempt:this.reconnectAttempt,delayMs:t}),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.stopped||this.connect().catch(e=>{this.emit("error",e instanceof Error?e:new Error(String(e)))})},t),"object"==typeof this.reconnectTimer&&"unref"in this.reconnectTimer&&this.reconnectTimer.unref()}reconnectNow(){this.stopped||(this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.close(),this.reconnectAttempt=0,this.connect().catch(e=>{this.emit("error",e instanceof Error?e:new Error(String(e)))}))}_sendConnect(e){const t=Date.now(),n=Date.now(),s="operator",i=["operator.admin","operator.approvals","operator.pairing"],o={id:this.deviceIdentity.deviceId,publicKey:this.deviceIdentity.publicKeyB64url,signedAt:n};if(e){const t=Date.now(),r=buildDeviceAuthPayload({deviceId:this.deviceIdentity.deviceId,clientId:"cli",clientMode:"cli",role:s,scopes:i,signedAtMs:n,token:this.config.token,nonce:e}),c=Date.now()-t,a=Date.now();o.nonce=e,o.signature=signDeviceAuth(this.deviceIdentity,r);const h=Date.now()-a;this.emit("transport",{event:"handshake.sign_done",buildPayloadMs:c,signMs:h})}const r=randomUUID(),c={type:"req",id:r,method:"connect",params:{minProtocol:3,maxProtocol:4,client:{id:"cli",displayName:"BeeOS Platform Bridge",version:"0.1.0",platform:"beeos",mode:"cli"},caps:this.config.openclawCanvasCompat?["tool-events","canvas"]:["tool-events"],role:s,scopes:i,auth:{token:this.config.token},device:o}},a=Date.now(),h=JSON.stringify(c),l=Date.now()-a,d=Date.now();this.ws?.send(h);const u=Date.now()-d;return this.emit("transport",{event:"handshake._sendConnect_done",totalMs:Date.now()-t,serializeMs:l,sendMs:u}),r}async request(e,t,n=6e4){if(!this.ws||!this.connected)throw new Error("Not connected to Gateway");const s=randomUUID(),i={type:"req",id:s,method:e,params:t};return new Promise((t,o)=>{const r=setTimeout(()=>{this.pending.delete(s),o(new Error(`Gateway request timeout: ${e}`))},n);this.pending.set(s,{resolve:t,reject:o,timer:r}),this.ws.send(JSON.stringify(i))})}async agentSend(e,t,n,s,i){const o={agentId:n,sessionKey:e,message:t,deliver:!1,idempotencyKey:randomUUID()};s?.length&&(o.attachments=s),i&&(o.inputProvenance=i);return await this.request("agent",o,12e4)}async chatSend(e,t,n){const s={sessionKey:e,message:t,deliver:!1,idempotencyKey:randomUUID()};n?.length&&(s.attachments=n);return await this.request("chat.send",s,12e4)}async chatAbort(e,t){const n={sessionKey:e};t&&(n.runId=t);return decodeChatAbortResult(await this.request("chat.abort",n))}sendFrame(e){if(!this.ws||!this.connected)return!1;try{return this.ws.send(JSON.stringify(e)),!0}catch{return!1}}agentCancel(e,t){const n={sessionKey:e};t?.runId&&(n.runId=t.runId);const s=`abort_${t?.runId??t?.requestId??e}`;return this.sendFrame({type:"req",id:s,method:"chat.abort",params:n})}async chatHistory(e,t){return this.request("chat.history",{sessionKey:e,limit:t})}async chatHistoryPage(e,t){const n=wsUrlToHttpBase(this.config.url);if(!n)return null;let s;try{s=new URL(`${n}/sessions/${encodeURIComponent(e)}/history`)}catch{return null}null!=t?.limit&&s.searchParams.set("limit",String(t.limit)),t?.cursor&&s.searchParams.set("cursor",t.cursor);try{const e=await fetch(s,{method:"GET",headers:{accept:"application/json",authorization:`Bearer ${this.config.token}`},signal:AbortSignal.timeout(1e4)});if(!e.ok)return null;const t=await e.json(),n=Array.isArray(t.messages)?t.messages:Array.isArray(t.items)?t.items:[];return{messages:n.filter(e=>"object"==typeof e&&null!==e),nextCursor:"string"==typeof t.nextCursor&&t.nextCursor.length>0?t.nextCursor:void 0,hasMore:!0===t.hasMore}}catch{return null}}async sessionsMessagesSubscribe(e){return this.request("sessions.messages.subscribe",{key:e})}async sessionsMessagesUnsubscribe(e){return this.request("sessions.messages.unsubscribe",{key:e})}async skillsStatus(e){return this.request("skills.status",e?{agentId:e}:{})}async skillsUpdate(e,t,n){return this.request("skills.update",{skillKey:e,enabled:t,...n?{agentId:n}:{}})}async sessionsList(){return this.request("sessions.list",{includeDerivedTitles:!0})}async sessionsResolve(e){return this.request("sessions.resolve",{sessionKey:e})}async sessionsPatch(e,t){return this.request("sessions.patch",{key:e,...t})}async sessionsDelete(e){return this.request("sessions.delete",{key:e})}async sessionsReset(e){return this.request("sessions.reset",{key:e,reason:"new"})}close(){if(this.ws)try{this.ws.close()}catch{}this.ws=null,this.connected=!1,this.connectPromise=null,this._currentConnectionId=null}get isConnected(){return this.connected}get connectionSeq(){return this._connectionSeq}get currentConnectionId(){return this._currentConnectionId}get lastReadyAt(){return this._lastReadyAt}}
1
+ import{WebSocket}from"ws";import{EventEmitter}from"node:events";import{randomUUID,createHash}from"node:crypto";import{loadOrCreateDeviceIdentity,buildDeviceAuthPayload,signDeviceAuth,ensureDevicePaired,warmupCryptoSign}from"./device-identity.js";const MIN_PROTOCOL_VERSION=3,MAX_PROTOCOL_VERSION=4,CONNECT_TIMEOUT_MS=3e4,HEARTBEAT_INTERVAL_MS=15e3,LIVENESS_TIMEOUT_MS=6e4,CHAT_HISTORY_PAGE_TIMEOUT_MS=1e4,AUTH_FAILURE_CLOSE_CODE=4001,EL_LAG_INTERVAL_MS=200,EL_LAG_THRESHOLD_MS=500,RECONNECT_NOTIFY_MIN_MS=6e4,RECONNECT_NOTIFY_MAX_MS=18e4,RECONNECT_NOTIFY_METHOD="server/reconnect";export class GatewayHandshakeError extends Error{code;retryable;retryAfterMs;constructor(e){super(e?.message||"Gateway connect rejected"),this.name="GatewayHandshakeError",this.code=e?.code,this.retryable=!0===e?.retryable||"UNAVAILABLE"===e?.code,this.retryAfterMs="number"==typeof e?.retryAfterMs?e.retryAfterMs:void 0}}export function decodeChatAbortResult(e){if("object"!=typeof e||null===e||Array.isArray(e))throw new Error("Invalid chat.abort response: expected object");const t=e;if("boolean"!=typeof t.aborted)throw new Error("Invalid chat.abort response: expected boolean aborted");if(!Array.isArray(t.runIds)||t.runIds.some(e=>"string"!=typeof e))throw new Error("Invalid chat.abort response: expected string[] runIds");return{aborted:t.aborted,runIds:[...t.runIds]}}function wsUrlToHttpBase(e){try{const t=new URL(e),n="wss:"===t.protocol?"https:":"ws:"===t.protocol?"http:":null;return n?`${n}//${t.host}`:null}catch{return null}}const MANUAL_RECONNECT_SIGNAL="SIGUSR1";function registerReconnectSignal(e,t,n){detachReconnectSignal(e),e.handler=t;try{process.on("SIGUSR1",t),n?.info?.("[gateway] manual reconnect enabled signal=SIGUSR1")}catch{n?.warn?.("[gateway] manual reconnect signal not supported")}}function detachReconnectSignal(e){if(e.handler){try{process.off("SIGUSR1",e.handler)}catch{}e.handler=null}}export class GatewayClient extends EventEmitter{ws=null;config;pending=new Map;connected=!1;stopped=!1;authFailed=!1;connectPromise=null;reconnectTimer=null;reconnectAttempt=0;nextReconnectDelayHintMs=null;signalState={handler:null};deviceIdentity;heartbeatTimer=null;lastPongTs=0;_connectionSeq=0;_currentConnectionId=null;_lastReadyAt=0;reconnectNotifyTimer=null;constructor(e){super(),this.config=e,this.deviceIdentity=loadOrCreateDeviceIdentity();try{ensureDevicePaired(this.deviceIdentity)}catch(e){}Date.now();warmupCryptoSign(this.deviceIdentity)}async start(){this.stopped=!1,this.reconnectAttempt=0,registerReconnectSignal(this.signalState,()=>{this.stopped||(this.emit("manual-reconnect"),this.reconnectNow())}),await this.connect()}stop(){this.stopped=!0,this.stopHeartbeat(),this.clearReconnectNotify(),detachReconnectSignal(this.signalState),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.close()}async connect(){if(this.connectPromise)return this.connectPromise;this.connectPromise=this._connect();try{await this.connectPromise}finally{this.connectPromise=null}}_connect(){return new Promise((e,t)=>{if(this.authFailed)return void t(new Error("Authentication failed — not retrying"));const n=Date.now();this.emit("transport",{event:"connect_start",url:this.config.url,attempt:this.reconnectAttempt});const s=new WebSocket(this.config.url);this.ws=s;let i=null;const r=setTimeout(()=>{this.emit("transport",{event:"connect_fail",reason:"timeout",durationMs:Date.now()-n}),s.close(),t(new Error("Gateway connection timeout"))},3e4),o=t=>{clearTimeout(r),this.connected=!0,this.reconnectAttempt=0,this.authFailed=!1,this._connectionSeq++,this._currentConnectionId=randomUUID(),this._lastReadyAt=Date.now(),this.startHeartbeat(),this.emit("transport",{event:"connect_ok",connectionSeq:this._connectionSeq,connectionId:this._currentConnectionId,durationMs:Date.now()-n}),this.emit("connected",{...t,connectionSeq:this._connectionSeq,connectionId:this._currentConnectionId}),e()},c=e=>{const i="string"==typeof e?e:e.message;clearTimeout(r),this.authFailed=!0,this.emit("transport",{event:"connect_fail",reason:i,durationMs:Date.now()-n}),this.emit("auth-failed",{reason:i}),s.close(),t("string"==typeof e?new Error(e):e)};s.on("open",()=>{this.emit("transport",{event:"ws_open",durationMs:Date.now()-n})}),s.on("message",e=>{const a="string"==typeof e?e:e.toString("utf-8");if("pong"===a)return void(this.lastPongTs=Date.now());let h;this.emitMessageHash(a);try{h=JSON.parse(a)}catch{return}const l=h.method;l&&!h.id&&(this.isReconnectNotification(l)?this.scheduleReconnectNotify():this.reconnectNotifyTimer&&this.clearReconnectNotify(),this.emit("notification",l,h.params));const d=h.type;if("event"===d&&this.reconnectNotifyTimer&&this.clearReconnectNotify(),"event"===d){const e=h.event,t=h.payload;if("connect.challenge"===e){const e=Date.now();this.emit("transport",{event:"handshake.challenge_recv",durationMs:e-n});const s=t?.nonce;i=this._sendConnect(s),this.emit("transport",{event:"handshake.connect_sent",durationMs:Date.now()-n});let r=0;const o=setInterval(()=>{r++},200),c=Date.now(),a=setInterval(()=>{const e=Date.now()-c,t=e-200*r;t>500&&this.emit("transport",{event:"eventloop_lag",lagMs:t,elapsedMs:e,probeSeq:r})},200),h=()=>{clearInterval(o),clearInterval(a),this.off("connected",h),this.off("disconnected",h)};setTimeout(h,3e4),this.once("connected",h),this.once("disconnected",h)}else"chat"===e?this.emit("chat",t):"agent"===e?this.emit("agent",t):"cron"===e?this.emit("cron",t):"canvas"===e?this.emit("canvas",t):"session.message"===e?this.emit("sessionMessage",t):this.emit("gateway-event",e,t)}else if("hello-ok"===d)this.emit("transport",{event:"handshake.hello_ok_recv",durationMs:Date.now()-n}),o(h);else if("hello-error"===d||"connect-error"===d)c("Authentication rejected by Gateway");else if("res"===d){const e=h.id;if(e&&e===i){if(this.emit("transport",{event:"handshake.connect_res_recv",ok:!!h.ok,durationMs:Date.now()-n}),h.ok){const e=h.payload;o(e??h)}else{const e=h.error,i=new GatewayHandshakeError(e);i.retryable?(clearTimeout(r),this.emit("transport",{event:"connect_fail",reason:i.message,retryable:!0,retryAfterMs:i.retryAfterMs,durationMs:Date.now()-n}),"number"==typeof i.retryAfterMs&&i.retryAfterMs>0&&(this.nextReconnectDelayHintMs=i.retryAfterMs),s.close(),t(i)):c(i)}return}const a=this.pending.get(e);if(a)if(this.pending.delete(e),clearTimeout(a.timer),h.ok)a.resolve(h.payload);else{const e=h.error;a.reject(new Error(e?.message||"Gateway request failed"))}else this.emit("response",h)}}),s.on("pong",()=>{this.lastPongTs=Date.now()}),s.on("close",e=>{const s=this.connected;this.stopHeartbeat(),this.clearReconnectNotify(),this.emit("transport",{event:"close",code:e,durationMs:Date.now()-n}),this.connected=!1,this.connectPromise=null;for(const[,e]of this.pending)clearTimeout(e.timer),e.reject(new Error("Connection closed"));this.pending.clear(),s||(clearTimeout(r),t(new Error(`Connection closed during handshake (code=${e})`))),4001===e&&(this.authFailed=!0,this.emit("auth-failed",{code:e})),this.emit("disconnected"),this.stopped||this.authFailed||this.scheduleReconnect()}),s.on("error",e=>{this.emit("transport",{event:"error",message:e.message,durationMs:Date.now()-n}),this.stopped?clearTimeout(r):(this.connected||(clearTimeout(r),t(e)),this.emit("error",e))})})}emitMessageHash(e){if(e.length<32)return;const t=createHash("sha256").update(e).digest("hex").slice(0,16);this.emit("transport",{event:"message_recv",hash:t,size:e.length})}startHeartbeat(){this.stopHeartbeat(),this.lastPongTs=Date.now(),this.heartbeatTimer=setInterval(()=>{if(!this.ws||!this.connected)return;try{this.ws.ping()}catch{}const e=Date.now()-this.lastPongTs;e>6e4&&(this.emit("liveness-timeout",{elapsed:e}),this.ws.close())},15e3),"object"==typeof this.heartbeatTimer&&"unref"in this.heartbeatTimer&&this.heartbeatTimer.unref()}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}isReconnectNotification(e){return"server/reconnect"===e||e.endsWith("/reconnect")}scheduleReconnectNotify(){this.clearReconnectNotify();const e=6e4+12e4*Math.random();this.emit("reconnect-notify-scheduled",{delayMs:Math.round(e)}),this.reconnectNotifyTimer=setTimeout(()=>{this.reconnectNotifyTimer=null,!this.stopped&&this.connected&&(this.emit("reconnect-notify-fired"),this.reconnectNow())},e),"object"==typeof this.reconnectNotifyTimer&&"unref"in this.reconnectNotifyTimer&&this.reconnectNotifyTimer.unref()}clearReconnectNotify(){this.reconnectNotifyTimer&&(clearTimeout(this.reconnectNotifyTimer),this.reconnectNotifyTimer=null)}scheduleReconnect(){if(this.stopped)return;if(this.reconnectTimer)return;const e=this.config.retry??{baseMs:1e3,maxMs:6e5,maxAttempts:0};if(e.maxAttempts>0&&this.reconnectAttempt>=e.maxAttempts)return void this.emit("reconnect-exhausted",this.reconnectAttempt);let t;null!==this.nextReconnectDelayHintMs&&this.nextReconnectDelayHintMs>0?(t=Math.min(this.nextReconnectDelayHintMs,e.maxMs),this.nextReconnectDelayHintMs=null):t=Math.min(e.baseMs*Math.pow(2,this.reconnectAttempt)+500*Math.random(),e.maxMs),this.reconnectAttempt++,this.emit("reconnecting",{attempt:this.reconnectAttempt,delayMs:t}),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.stopped||this.connect().catch(e=>{this.emitReconnectFailure(e)})},t),"object"==typeof this.reconnectTimer&&"unref"in this.reconnectTimer&&this.reconnectTimer.unref()}reconnectNow(){this.stopped||(this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.close(),this.reconnectAttempt=0,this.connect().catch(e=>{this.emitReconnectFailure(e)}))}emitReconnectFailure(e){this.stopped||this.authFailed||this.emit("error",e instanceof Error?e:new Error(String(e)))}_sendConnect(e){const t=Date.now(),n=Date.now(),s="operator",i=["operator.admin","operator.approvals","operator.pairing"],r={id:this.deviceIdentity.deviceId,publicKey:this.deviceIdentity.publicKeyB64url,signedAt:n};if(e){const t=Date.now(),o=buildDeviceAuthPayload({deviceId:this.deviceIdentity.deviceId,clientId:"cli",clientMode:"cli",role:s,scopes:i,signedAtMs:n,token:this.config.token,nonce:e}),c=Date.now()-t,a=Date.now();r.nonce=e,r.signature=signDeviceAuth(this.deviceIdentity,o);const h=Date.now()-a;this.emit("transport",{event:"handshake.sign_done",buildPayloadMs:c,signMs:h})}const o=randomUUID(),c={type:"req",id:o,method:"connect",params:{minProtocol:3,maxProtocol:4,client:{id:"cli",displayName:"BeeOS Platform Bridge",version:"0.1.0",platform:"beeos",mode:"cli"},caps:this.config.openclawCanvasCompat?["tool-events","canvas"]:["tool-events"],role:s,scopes:i,auth:{token:this.config.token},device:r}},a=Date.now(),h=JSON.stringify(c),l=Date.now()-a,d=Date.now();this.ws?.send(h);const u=Date.now()-d;return this.emit("transport",{event:"handshake._sendConnect_done",totalMs:Date.now()-t,serializeMs:l,sendMs:u}),o}async request(e,t,n=6e4){if(!this.ws||!this.connected)throw new Error("Not connected to Gateway");const s=randomUUID(),i={type:"req",id:s,method:e,params:t};return new Promise((t,r)=>{const o=setTimeout(()=>{this.pending.delete(s),r(new Error(`Gateway request timeout: ${e}`))},n);this.pending.set(s,{resolve:t,reject:r,timer:o}),this.ws.send(JSON.stringify(i))})}async agentSend(e,t,n,s,i){const r={agentId:n,sessionKey:e,message:t,deliver:!1,idempotencyKey:randomUUID()};s?.length&&(r.attachments=s),i&&(r.inputProvenance=i);return await this.request("agent",r,12e4)}async chatSend(e,t,n){const s={sessionKey:e,message:t,deliver:!1,idempotencyKey:randomUUID()};n?.length&&(s.attachments=n);return await this.request("chat.send",s,12e4)}async chatAbort(e,t){const n={sessionKey:e};t&&(n.runId=t);return decodeChatAbortResult(await this.request("chat.abort",n))}sendFrame(e){if(!this.ws||!this.connected)return!1;try{return this.ws.send(JSON.stringify(e)),!0}catch{return!1}}agentCancel(e,t){const n={sessionKey:e};t?.runId&&(n.runId=t.runId);const s=`abort_${t?.runId??t?.requestId??e}`;return this.sendFrame({type:"req",id:s,method:"chat.abort",params:n})}async chatHistory(e,t){return this.request("chat.history",{sessionKey:e,limit:t})}async chatHistoryPage(e,t){const n=wsUrlToHttpBase(this.config.url);if(!n)return null;let s;try{s=new URL(`${n}/sessions/${encodeURIComponent(e)}/history`)}catch{return null}null!=t?.limit&&s.searchParams.set("limit",String(t.limit)),t?.cursor&&s.searchParams.set("cursor",t.cursor);try{const e=await fetch(s,{method:"GET",headers:{accept:"application/json",authorization:`Bearer ${this.config.token}`},signal:AbortSignal.timeout(1e4)});if(!e.ok)return null;const t=await e.json(),n=Array.isArray(t.messages)?t.messages:Array.isArray(t.items)?t.items:[];return{messages:n.filter(e=>"object"==typeof e&&null!==e),nextCursor:"string"==typeof t.nextCursor&&t.nextCursor.length>0?t.nextCursor:void 0,hasMore:!0===t.hasMore}}catch{return null}}async sessionsMessagesSubscribe(e){return this.request("sessions.messages.subscribe",{key:e})}async sessionsMessagesUnsubscribe(e){return this.request("sessions.messages.unsubscribe",{key:e})}async skillsStatus(e){return this.request("skills.status",e?{agentId:e}:{})}async skillsUpdate(e,t,n){return this.request("skills.update",{skillKey:e,enabled:t,...n?{agentId:n}:{}})}async sessionsList(){return this.request("sessions.list",{includeDerivedTitles:!0})}async sessionsResolve(e){return this.request("sessions.resolve",{sessionKey:e})}async sessionsPatch(e,t){return this.request("sessions.patch",{key:e,...t})}async sessionsDelete(e){return this.request("sessions.delete",{key:e})}async sessionsReset(e){return this.request("sessions.reset",{key:e,reason:"new"})}close(){if(this.ws)try{this.ws.close()}catch{}this.ws=null,this.connected=!1,this.connectPromise=null,this._currentConnectionId=null}get isConnected(){return this.connected}get connectionSeq(){return this._connectionSeq}get currentConnectionId(){return this._currentConnectionId}get lastReadyAt(){return this._lastReadyAt}}
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);e.logger.error?.("[beeos-claw] unhandledRejection:",t),r({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:o,error:{code:"unhandled_rejection",message:o}})}),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));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"))});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: %s",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}),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);e.logger.error?.("[beeos-claw] unhandledRejection:",o),r({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:t,error:{code:"unhandled_rejection",message:t}})}),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));const d=sharedAcpServer;o.current=d;const p=sharedPromptRunStore,{enabled:u,url:m,principalId:f,a2aEnabled:y}=t.messageBus;let w;t.agentGatewayUrl&&agentKeys&&(w=createTokenProvider({agentGatewayUrl:t.agentGatewayUrl,identity:agentKeyPairToIdentity(agentKeys)}));const b=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})}:{},...b&&t.agentGatewayUrl&&i&&process.env.MESSAGE_RUNTIME_DELIVERY_KEY?.trim()?{transportAdapter:new RuntimeServiceTransportReadinessAdapter({agentGatewayUrl:t.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:()=>e.logger.error?.("[runtime-service] delivery worker failed before acknowledgement; delivery retained for retry")});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 h=u&&(w||t.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: %s",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}]:[]},u=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:u(r),promptRunStore:p,sessionMutationCoordinator:g,logger:e.logger,writeObs:a,principalIdOverride:r.principalId,promptTimeoutMs:t.promptTimeoutMs}),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 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(()=>{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&&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 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 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{RUNTIME_HASH_DOMAINS}from"@beeos-ai/beeos-types/runtime";import{runtimeMessageHash,semanticEqual,validateRuntimeCancelEnvelope,validateRuntimeRequestEnvelope}from"./runtime-message-channel-contract.js";export class RuntimeMessageChannelProtocolError extends Error{status;code;constructor(e,t,o){super(e),this.status=t,this.code=o,this.name="RuntimeMessageChannelProtocolError"}}export class MessageServiceRuntimeMessageChannel{options;constructor(e){if(this.options=e,!e.scopedDeliveryKey)throw new Error("Message runtime delivery credential is required")}async read(e){const t=await this.post("/api/v1/runtime/deliveries/read",e.lease,{schemaVersion:1,maxCount:e.maxCount,blockMs:e.blockMs},void 0,e.signal);if(!record(t)||!exactKeys(t,["status","deliveries"])||"deliveries"!==t.status||!Array.isArray(t.deliveries)||t.deliveries.length>e.maxCount||!t.deliveries.every(isDelivery)||new Set(t.deliveries.map(e=>e.deliveryId)).size!==t.deliveries.length)throw new RuntimeMessageChannelProtocolError("Message Service returned invalid runtime deliveries");return t.deliveries}async renew(e){const t=await this.post("/api/v1/runtime/deliveries/renew",e.lease,{schemaVersion:1,deliveryIds:e.deliveryIds});if(!(record(t)&&exactKeys(t,["status","renewed","notPending"])&&"renewed"===t.status&&strings(t.renewed)&&strings(t.notPending)&&exactIdPartition(e.deliveryIds,t.renewed,t.notPending)))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid delivery renewal");return{renewed:t.renewed,notPending:t.notPending}}async acknowledge(e){const t=await this.post("/api/v1/runtime/deliveries/ack",e.lease,{schemaVersion:1,deliveryIds:e.deliveryIds});if(!(record(t)&&exactKeys(t,["status","deliveryIds"])&&"acknowledged"===t.status&&strings(t.deliveryIds)&&sameIds(t.deliveryIds,e.deliveryIds)))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid delivery acknowledgement")}async history(e){const t=`/api/v1/runtime/operations/${encodeURIComponent(e.operationId)}/history`,o=await this.fetch(t,e.lease,{method:"GET"});if(404===o.status)return{status:"not_found"};if(410===o.status)return{status:"expired"};const n=await responseJson(o);if(!o.ok)throw protocolFailure(o,n,"runtime history read failed");if(!isHistory(n,e.operationId))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid operation history");return{status:"found",snapshot:n}}async append(e){const t=`/api/v1/runtime/operations/${encodeURIComponent(e.operationId)}/messages`,o=appendResult(await this.post(t,e.lease,{schemaVersion:1,type:e.type,payload:e.payload},e.executionGrant));if(o.message.channel_id!==e.operationId||o.message.type!==e.type||o.message.in_reply_to!==e.payload.requestMessageId||!semanticEqual(o.message.payload,e.payload))throw new RuntimeMessageChannelProtocolError("Message Service append response correlation mismatch");return o}async post(e,t,o,n,r){const i=await this.fetch(e,t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(o),...r?{signal:r}:{}},n),s=await responseJson(i);if(!i.ok)throw protocolFailure(i,s,`runtime message-channel POST ${e} failed`);return s}async fetch(e,t,o,n){return await this.options.transport.fetchRuntimeChannel(new URL(e,this.options.serviceUrl),{runtimeLeaseCredential:t.runtimeLeaseCredential,scopedDeliveryKey:this.options.scopedDeliveryKey,...n?{executionGrant:n}:{}},o)}}function appendResult(e){if(!record(e)||!exactKeys(e,["status","disposition","message"])||"persisted"!==e.status||"created"!==e.disposition&&"reused"!==e.disposition||!envelope(e.message))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid append result");return{disposition:e.disposition,message:e.message}}function protocolFailure(e,t,o){const n=record(t)&&"string"==typeof t.code?t.code:void 0,r=record(t)&&"string"==typeof t.message?t.message:o;return new RuntimeMessageChannelProtocolError(`${r} (${e.status})`,e.status,n)}async function responseJson(e){try{return await e.json()}catch{return}}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function strings(e){return Array.isArray(e)&&e.every(e=>"string"==typeof e)}function sameIds(e,t){return e.length===t.length&&[...e].sort().every((e,o)=>e===[...t].sort()[o])}function exactIdPartition(e,...t){return new Set(e).size===e.length&&!t.some(e=>new Set(e).size!==e.length)&&sameIds(t.flat(),e)}function envelope(e){if(!record(e))return!1;return exactKeys(e,["message_id","offset","channel_id","type","payload","publisher_id","idempotency_key","created_at","state",...void 0===e.in_reply_to?[]:["in_reply_to"]])&&"string"==typeof e.message_id&&Number.isInteger(e.offset)&&e.offset>=1&&"string"==typeof e.channel_id&&["runtime_rpc_request","runtime_operation_started","runtime_operation_progress","runtime_effect_committed","runtime_projection_pending","runtime_projection_synced","runtime_projection_blocked","runtime_rpc_response","runtime_operation_cancel_requested"].includes(e.type)&&record(e.payload)&&"string"==typeof e.publisher_id&&"string"==typeof e.idempotency_key&&"string"==typeof e.created_at&&"completed"===e.state}function isDelivery(e){return record(e)&&exactKeys(e,["deliveryId","redelivered","idleMs","message",...void 0===e.executionGrant?[]:["executionGrant"]])&&"string"==typeof e.deliveryId&&"boolean"==typeof e.redelivered&&Number.isInteger(e.idleMs)&&e.idleMs>=0&&envelope(e.message)&&(void 0===e.executionGrant||"string"==typeof e.executionGrant&&/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(e.executionGrant))&&["runtime_rpc_request","runtime_operation_cancel_requested"].includes(e.message.type)}function isHistory(e,t){if(!record(e))return!1;const o=["schemaVersion","operationId","requestMessageId","requestHash","status","terminal","terminalSource","cancelRequested","latestOffset","projectionState","request","messages",...["started","latestProgress","effectCommitted","projectionPending","projectionSynced","projectionBlocked","cancel","final"].filter(t=>void 0!==e[t])];if(!(exactKeys(e,o)&&1===e.schemaVersion&&"string"==typeof e.operationId&&e.operationId===t&&"string"==typeof e.requestMessageId&&"string"==typeof e.requestHash&&["queued","running","runtime_committed","projection_pending","projection_blocked","succeeded","failed","cancelled","expired","outcome_unknown"].includes(e.status)&&"boolean"==typeof e.terminal&&["none","final_message","deadline"].includes(e.terminalSource)&&"boolean"==typeof e.cancelRequested&&["","reserved","pending","synced","blocked","not_applicable"].includes(e.projectionState)&&Array.isArray(e.messages)&&e.messages.every(envelope)&&Number.isInteger(e.latestOffset)&&!(e.latestOffset<1)))return!1;const n=validateRuntimeRequestEnvelope(e.request);if(!n.ok||e.request.channel_id!==e.operationId||n.envelope.payload.operationId!==e.operationId||n.envelope.payload.requestMessageId!==e.requestMessageId||n.envelope.payload.requestHash!==e.requestHash)return!1;const r=n.envelope.payload;if(void 0!==e.started&&!correlatedEnvelope(e.started,"runtime_operation_started",r,["schemaVersion","operationId","requestMessageId","requestHash","executionGrantHash","handlerIdentity","dispatchEpoch","method","executionMode","claimedAt"]))return!1;if(void 0!==e.latestProgress&&!correlatedEnvelope(e.latestProgress,"runtime_operation_progress",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","phase","progress"]))return!1;if(!(void 0===e.effectCommitted||correlatedEnvelope(e.effectCommitted,"runtime_effect_committed",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","effectRecord","effectRecordHash","committedAt"])&&validEffectCommitted(e.effectCommitted.payload,r)))return!1;if(void 0!==e.projectionPending&&!correlatedEnvelope(e.projectionPending,"runtime_projection_pending",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision"]))return!1;if(void 0!==e.projectionSynced&&!correlatedEnvelope(e.projectionSynced,"runtime_projection_synced",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision",...void 0===e.projectionSynced.payload.platformAgentId?[]:["platformAgentId"]]))return!1;if(void 0!==e.projectionBlocked&&!correlatedEnvelope(e.projectionBlocked,"runtime_projection_blocked",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision","errorCode",...void 0===e.projectionBlocked.payload.causeCode?[]:["causeCode"]]))return!1;if(void 0!==e.cancel){const t=validateRuntimeCancelEnvelope(e.cancel);if(!t.ok||!sameCorrelation(t.envelope,r))return!1}if(void 0!==e.final&&!correlatedEnvelope(e.final,"runtime_rpc_response",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","outcome","response","completedAt",...void 0===e.final.payload.serviceResultHash?[]:["serviceResultHash"],...void 0===e.final.payload.effectRecordHash?[]:["effectRecordHash"]]))return!1;const i=e.messages;if(0===i.length||e.latestOffset!==i.length||i.some((t,o)=>t.offset!==o+1||t.channel_id!==e.operationId)||!semanticEqual(i[0],e.request)||!historyMessagesMatchSummaries(i,e)||!projectionStateMatches(e))return!1;if(e.terminal!==("none"!==e.terminalSource)||void 0!==e.effectCommitted&&void 0===e.started||"queued"===e.status&&(void 0!==e.started||void 0!==e.effectCommitted)||["running","runtime_committed","projection_pending","projection_blocked"].includes(e.status)&&void 0===e.started)return!1;if("none"===e.terminalSource)return void 0===e.final&&["queued","running","runtime_committed","projection_pending","projection_blocked"].includes(e.status);if("deadline"===e.terminalSource)return"expired"===e.status&&void 0===e.final;if(!envelope(e.final))return!1;const s=e.final.payload.outcome,a="succeeded"===s?"succeeded":"cancelled"===s?"cancelled":"expired"===s?"expired":"outcome_unknown"===s?"outcome_unknown":"projection_blocked"===s?"projection_blocked":"failed"===s?"failed":void 0;return e.status===a}function validEffectCommitted(e,t){const o=e.effectRecord;return!!(record(o)&&exactKeys(o,["schemaVersion","operationId","method","coreResultRef","coreResultHash",...void 0===o.projectionRevision?[]:["projectionRevision"]])&&1===o.schemaVersion&&o.operationId===t.operationId&&o.method===t.method&&"string"==typeof o.coreResultRef&&0!==o.coreResultRef.length&&"string"==typeof o.coreResultHash&&/^[0-9a-f]{64}$/.test(o.coreResultHash)&&(void 0===o.projectionRevision||"string"==typeof o.projectionRevision&&/^(0|[1-9][0-9]*)$/.test(o.projectionRevision))&&"string"==typeof e.effectRecordHash&&/^[0-9a-f]{64}$/.test(e.effectRecordHash)&&runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,o)===e.effectRecordHash&&"string"==typeof e.committedAt&&Number.isFinite(Date.parse(e.committedAt)))}function projectionStateMatches(e){const t=e.effectCommitted?.payload?.effectRecord?.projectionRevision,o=e=>record(e)&&record(e.payload)&&e.payload.revision===t;switch(e.projectionState){case"":return void 0===e.effectCommitted&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&["queued","running","succeeded","failed","cancelled","expired","outcome_unknown"].includes(e.status);case"not_applicable":return void 0!==e.effectCommitted&&void 0===t&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&["runtime_committed","succeeded","outcome_unknown"].includes(e.status);case"reserved":return void 0!==e.effectCommitted&&void 0!==t&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&"runtime_committed"===e.status;case"pending":return void 0!==t&&o(e.projectionPending)&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&"projection_pending"===e.status;case"synced":return void 0!==t&&o(e.projectionPending)&&o(e.projectionSynced)&&void 0===e.projectionBlocked&&["runtime_committed","succeeded"].includes(e.status);case"blocked":return void 0!==t&&o(e.projectionPending)&&void 0===e.projectionSynced&&o(e.projectionBlocked)&&"projection_blocked"===e.status;default:return!1}}function historyMessagesMatchSummaries(e,t){const o=t=>e.filter(e=>e.type===t),n=(e,t,n=1)=>{const r=o(e);return r.length<=n&&(void 0===t?0===r.length:r.length>0&&semanticEqual(t,r.at(-1)))};if(!(n("runtime_rpc_request",t.request)&&n("runtime_operation_started",t.started)&&n("runtime_effect_committed",t.effectCommitted)&&n("runtime_projection_pending",t.projectionPending)&&n("runtime_projection_synced",t.projectionSynced)&&n("runtime_projection_blocked",t.projectionBlocked)&&n("runtime_operation_cancel_requested",t.cancel)&&n("runtime_rpc_response",t.final)))return!1;const r=o("runtime_operation_progress");if(void 0===t.latestProgress?0!==r.length:0===r.length||!semanticEqual(t.latestProgress,r.at(-1)))return!1;if(t.cancelRequested!==(void 0!==t.cancel))return!1;const i=e.findIndex(e=>"runtime_rpc_response"===e.type);return-1===i||i===e.length-1}function correlatedEnvelope(e,t,o,n){return!(!envelope(e)||e.type!==t||!exactKeys(e.payload,n)||e.in_reply_to!==o.requestMessageId)&&sameCorrelation(e,o)}function sameCorrelation(e,t){const o=e.payload;return e.channel_id===t.operationId&&o.operationId===t.operationId&&o.requestMessageId===t.requestMessageId&&o.requestHash===t.requestHash&&o.dispatchEpoch===t.dispatchEpoch&&o.method===t.method}function exactKeys(e,t){const o=Object.keys(e).sort(),n=[...t].sort();return o.length===n.length&&o.every((e,t)=>e===n[t])}
1
+ import{RUNTIME_HASH_DOMAINS}from"@beeos-ai/beeos-types/runtime";import{runtimeMessageHash,semanticEqual,validateRuntimeCancelEnvelope,validateRuntimeRequestEnvelope}from"./runtime-message-channel-contract.js";export class RuntimeMessageChannelProtocolError extends Error{status;code;constructor(e,t,o){super(e),this.status=t,this.code=o,this.name="RuntimeMessageChannelProtocolError"}}export class MessageServiceRuntimeMessageChannel{options;constructor(e){if(this.options=e,!e.scopedDeliveryKey)throw new Error("Message runtime delivery credential is required")}async read(e){const t=await this.post("/api/v1/runtime/deliveries/read",e.lease,{schemaVersion:1,maxCount:e.maxCount,blockMs:e.blockMs},void 0,e.signal);if(!record(t)||!exactKeys(t,["status","deliveries"])||"deliveries"!==t.status||!Array.isArray(t.deliveries)||t.deliveries.length>e.maxCount||!t.deliveries.every(isDelivery)||new Set(t.deliveries.map(e=>e.deliveryId)).size!==t.deliveries.length)throw new RuntimeMessageChannelProtocolError("Message Service returned invalid runtime deliveries");return t.deliveries}async renew(e){const t=await this.post("/api/v1/runtime/deliveries/renew",e.lease,{schemaVersion:1,deliveryIds:e.deliveryIds});if(!(record(t)&&exactKeys(t,["status","renewed","notPending"])&&"renewed"===t.status&&strings(t.renewed)&&strings(t.notPending)&&exactIdPartition(e.deliveryIds,t.renewed,t.notPending)))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid delivery renewal");return{renewed:t.renewed,notPending:t.notPending}}async acknowledge(e){const t=await this.post("/api/v1/runtime/deliveries/ack",e.lease,{schemaVersion:1,deliveryIds:e.deliveryIds});if(!(record(t)&&exactKeys(t,["status","deliveryIds"])&&"acknowledged"===t.status&&strings(t.deliveryIds)&&sameIds(t.deliveryIds,e.deliveryIds)))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid delivery acknowledgement")}async history(e){const t=`/api/v1/runtime/operations/${encodeURIComponent(e.operationId)}/history`,o=await this.fetch(t,e.lease,{method:"GET"});if(404===o.status)return{status:"not_found"};if(410===o.status)return{status:"expired"};const n=await responseJson(o);if(!o.ok)throw protocolFailure(o,n,"runtime history read failed");if(!isHistory(n,e.operationId))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid operation history");return{status:"found",snapshot:n}}async append(e){const t=`/api/v1/runtime/operations/${encodeURIComponent(e.operationId)}/messages`,o=appendResult(await this.post(t,e.lease,{schemaVersion:1,type:e.type,payload:e.payload},e.executionGrant)),n="runtime_rpc_response"===e.type?e.payload.requestMessageId:void 0;if(o.message.channel_id!==e.operationId||o.message.type!==e.type||o.message.in_reply_to!==n||!semanticEqual(o.message.payload,e.payload))throw new RuntimeMessageChannelProtocolError("Message Service append response correlation mismatch");return o}async post(e,t,o,n,r){const i=await this.fetch(e,t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(o),...r?{signal:r}:{}},n),s=await responseJson(i);if(!i.ok)throw protocolFailure(i,s,`runtime message-channel POST ${e} failed`);return s}async fetch(e,t,o,n){return await this.options.transport.fetchRuntimeChannel(new URL(e,this.options.serviceUrl),{runtimeLeaseCredential:t.runtimeLeaseCredential,scopedDeliveryKey:this.options.scopedDeliveryKey,...n?{executionGrant:n}:{}},o)}}function appendResult(e){if(!record(e)||!exactKeys(e,["status","disposition","message"])||"persisted"!==e.status||"created"!==e.disposition&&"reused"!==e.disposition||!envelope(e.message))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid append result");return{disposition:e.disposition,message:e.message}}function protocolFailure(e,t,o){const n=record(t)&&"string"==typeof t.code?t.code:void 0,r=record(t)&&"string"==typeof t.message?t.message:o;return new RuntimeMessageChannelProtocolError(`${r} (${e.status})`,e.status,n)}async function responseJson(e){try{return await e.json()}catch{return}}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function strings(e){return Array.isArray(e)&&e.every(e=>"string"==typeof e)}function sameIds(e,t){return e.length===t.length&&[...e].sort().every((e,o)=>e===[...t].sort()[o])}function exactIdPartition(e,...t){return new Set(e).size===e.length&&!t.some(e=>new Set(e).size!==e.length)&&sameIds(t.flat(),e)}function envelope(e){if(!record(e))return!1;return exactKeys(e,["message_id","offset","channel_id","type","payload","publisher_id","idempotency_key","created_at","state",...void 0===e.in_reply_to?[]:["in_reply_to"]])&&"string"==typeof e.message_id&&Number.isInteger(e.offset)&&e.offset>=1&&"string"==typeof e.channel_id&&["runtime_rpc_request","runtime_operation_started","runtime_operation_progress","runtime_effect_committed","runtime_projection_pending","runtime_projection_synced","runtime_projection_blocked","runtime_rpc_response","runtime_operation_cancel_requested"].includes(e.type)&&record(e.payload)&&"string"==typeof e.publisher_id&&"string"==typeof e.idempotency_key&&"string"==typeof e.created_at&&"completed"===e.state}function isDelivery(e){return record(e)&&exactKeys(e,["deliveryId","redelivered","idleMs","message",...void 0===e.executionGrant?[]:["executionGrant"]])&&"string"==typeof e.deliveryId&&"boolean"==typeof e.redelivered&&Number.isInteger(e.idleMs)&&e.idleMs>=0&&envelope(e.message)&&(void 0===e.executionGrant||"string"==typeof e.executionGrant&&/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(e.executionGrant))&&["runtime_rpc_request","runtime_operation_cancel_requested"].includes(e.message.type)}function isHistory(e,t){if(!record(e))return!1;const o=["schemaVersion","operationId","requestMessageId","requestHash","status","effectState","terminal","terminalSource","cancelRequested","latestOffset","projectionState","request","messages",...["started","latestProgress","effectCommitted","projectionPending","projectionSynced","projectionBlocked","cancel","final"].filter(t=>void 0!==e[t])];if(!(exactKeys(e,o)&&1===e.schemaVersion&&"string"==typeof e.operationId&&e.operationId===t&&"string"==typeof e.requestMessageId&&"string"==typeof e.requestHash&&["queued","running","runtime_committed","projection_pending","projection_blocked","succeeded","failed","cancelled","expired","outcome_unknown"].includes(e.status)&&["","committed"].includes(e.effectState)&&"boolean"==typeof e.terminal&&["none","final_message","deadline"].includes(e.terminalSource)&&"boolean"==typeof e.cancelRequested&&["","reserved","pending","synced","blocked","not_applicable"].includes(e.projectionState)&&Array.isArray(e.messages)&&e.messages.every(envelope)&&Number.isInteger(e.latestOffset)&&!(e.latestOffset<1)))return!1;const n=validateRuntimeRequestEnvelope(e.request);if(!n.ok||e.request.channel_id!==e.operationId||n.envelope.payload.operationId!==e.operationId||n.envelope.payload.requestMessageId!==e.requestMessageId||n.envelope.payload.requestHash!==e.requestHash)return!1;const r=n.envelope.payload;if(void 0!==e.started&&!correlatedEnvelope(e.started,"runtime_operation_started",r,["schemaVersion","operationId","requestMessageId","requestHash","executionGrantHash","handlerIdentity","dispatchEpoch","method","executionMode","claimedAt"]))return!1;if(void 0!==e.latestProgress&&!correlatedEnvelope(e.latestProgress,"runtime_operation_progress",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","phase","progress"]))return!1;if(!(void 0===e.effectCommitted||correlatedEnvelope(e.effectCommitted,"runtime_effect_committed",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","effectRecord","effectRecordHash","committedAt"])&&validEffectCommitted(e.effectCommitted.payload,r)))return!1;if(void 0!==e.projectionPending&&!correlatedEnvelope(e.projectionPending,"runtime_projection_pending",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision"]))return!1;if(void 0!==e.projectionSynced&&!correlatedEnvelope(e.projectionSynced,"runtime_projection_synced",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision",...void 0===e.projectionSynced.payload.platformAgentId?[]:["platformAgentId"]]))return!1;if(void 0!==e.projectionBlocked&&!correlatedEnvelope(e.projectionBlocked,"runtime_projection_blocked",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision","errorCode",...void 0===e.projectionBlocked.payload.causeCode?[]:["causeCode"]]))return!1;if(void 0!==e.cancel){const t=validateRuntimeCancelEnvelope(e.cancel);if(!t.ok||!sameCorrelation(t.envelope,r))return!1}if(void 0!==e.final&&!correlatedEnvelope(e.final,"runtime_rpc_response",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","outcome","response","completedAt",...void 0===e.final.payload.serviceResultHash?[]:["serviceResultHash"],...void 0===e.final.payload.effectRecordHash?[]:["effectRecordHash"]]))return!1;const i=e.messages;if(0===i.length||e.latestOffset!==i.length||i.some((t,o)=>t.offset!==o+1||t.channel_id!==e.operationId)||!semanticEqual(i[0],e.request)||!historyMessagesMatchSummaries(i,e)||!projectionStateMatches(e))return!1;if(e.terminal!==("none"!==e.terminalSource)||"committed"===e.effectState!=(void 0!==e.effectCommitted)||void 0!==e.effectCommitted&&void 0===e.started||"queued"===e.status&&(void 0!==e.started||void 0!==e.effectCommitted)||["running","runtime_committed","projection_pending","projection_blocked"].includes(e.status)&&void 0===e.started)return!1;if("none"===e.terminalSource)return void 0===e.final&&["queued","running","runtime_committed","projection_pending","projection_blocked"].includes(e.status);if("deadline"===e.terminalSource)return void 0===e.final&&(void 0===e.started?"expired"===e.status:"outcome_unknown"===e.status);if(!envelope(e.final))return!1;const s=e.final.payload.outcome,a="succeeded"===s?"succeeded":"cancelled"===s?"cancelled":"expired"===s?"expired":"outcome_unknown"===s?"outcome_unknown":"projection_blocked"===s?"projection_blocked":"failed"===s?"failed":void 0;return e.status===a}function validEffectCommitted(e,t){const o=e.effectRecord;return!!(record(o)&&exactKeys(o,["schemaVersion","operationId","method","coreResultRef","coreResultHash",...void 0===o.projectionRevision?[]:["projectionRevision"]])&&1===o.schemaVersion&&o.operationId===t.operationId&&o.method===t.method&&"string"==typeof o.coreResultRef&&0!==o.coreResultRef.length&&"string"==typeof o.coreResultHash&&/^[0-9a-f]{64}$/.test(o.coreResultHash)&&(void 0===o.projectionRevision||"string"==typeof o.projectionRevision&&/^(0|[1-9][0-9]*)$/.test(o.projectionRevision))&&"string"==typeof e.effectRecordHash&&/^[0-9a-f]{64}$/.test(e.effectRecordHash)&&runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,o)===e.effectRecordHash&&"string"==typeof e.committedAt&&Number.isFinite(Date.parse(e.committedAt)))}function projectionStateMatches(e){const t=e.effectCommitted?.payload?.effectRecord?.projectionRevision,o="deadline"===e.terminalSource,n=e=>record(e)&&record(e.payload)&&e.payload.revision===t;switch(e.projectionState){case"":return void 0===e.effectCommitted&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||["queued","running","succeeded","failed","cancelled","expired","outcome_unknown"].includes(e.status));case"not_applicable":return void 0!==e.effectCommitted&&void 0===t&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||["runtime_committed","succeeded","outcome_unknown"].includes(e.status));case"reserved":return void 0!==e.effectCommitted&&void 0!==t&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||"runtime_committed"===e.status);case"pending":return void 0!==t&&n(e.projectionPending)&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||"projection_pending"===e.status);case"synced":return void 0!==t&&n(e.projectionPending)&&n(e.projectionSynced)&&void 0===e.projectionBlocked&&(o||["runtime_committed","succeeded"].includes(e.status));case"blocked":return void 0!==t&&n(e.projectionPending)&&void 0===e.projectionSynced&&n(e.projectionBlocked)&&(o||"projection_blocked"===e.status);default:return!1}}function historyMessagesMatchSummaries(e,t){const o=t=>e.filter(e=>e.type===t),n=(e,t,n=1)=>{const r=o(e);return r.length<=n&&(void 0===t?0===r.length:r.length>0&&semanticEqual(t,r.at(-1)))};if(!(n("runtime_rpc_request",t.request)&&n("runtime_operation_started",t.started)&&n("runtime_effect_committed",t.effectCommitted)&&n("runtime_projection_pending",t.projectionPending)&&n("runtime_projection_synced",t.projectionSynced)&&n("runtime_projection_blocked",t.projectionBlocked)&&n("runtime_operation_cancel_requested",t.cancel)&&n("runtime_rpc_response",t.final)))return!1;const r=o("runtime_operation_progress");if(void 0===t.latestProgress?0!==r.length:0===r.length||!semanticEqual(t.latestProgress,r.at(-1)))return!1;if(t.cancelRequested!==(void 0!==t.cancel))return!1;const i=e.findIndex(e=>"runtime_rpc_response"===e.type);return-1===i||i===e.length-1}function correlatedEnvelope(e,t,o,n){const r="runtime_rpc_response"===t?o.requestMessageId:void 0;return!(!envelope(e)||e.type!==t||!exactKeys(e.payload,n)||e.in_reply_to!==r)&&sameCorrelation(e,o)}function sameCorrelation(e,t){const o=e.payload;return e.channel_id===t.operationId&&o.operationId===t.operationId&&o.requestMessageId===t.requestMessageId&&o.requestHash===t.requestHash&&o.dispatchEpoch===t.dispatchEpoch&&o.method===t.method}function exactKeys(e,t){const o=Object.keys(e).sort(),n=[...t].sort();return o.length===n.length&&o.every((e,t)=>e===n[t])}
@@ -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";export class MsRuntimeMessageChannelAdapter{dispatcher;channel;verifier;ports;journal;getLease;options;controller;running;workers=new Map;poisonJournal;receiptHistory=this;constructor(e,t,a,n,o,i,r){if(this.dispatcher=e,this.channel=t,this.verifier=a,this.ports=n,this.journal=o,this.getLease=i,this.options=r,!r.poisonJournal)throw new Error("durable poison journal is required");if(this.poisonJournal=r.poisonJournal,!Number.isSafeInteger(r.poisonDeliveryLimit??3)||(r.poisonDeliveryLimit??3)<=0)throw new Error("poison delivery limit must be a positive integer")}start(){if(this.running)return;this.controller=new AbortController;const e=this.loop(this.controller.signal).finally(()=>{this.running===e&&(this.running=void 0)});this.running=e}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(()=>{})},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 a;try{a=await this.channel.read({maxCount:this.options.readCount??32,blockMs:this.options.blockMs??5e3,lease:t,signal:e})}catch{if(e.aborted)return;await delay(this.options.idleDelayMs??500,e);continue}for(const n of a){if(e.aborted)break;if(n.message.type===RUNTIME_MESSAGE_TYPES.cancel){await this.handleCancel(n,t).catch(()=>{});continue}const a=n.message.channel_id;if(this.workers.has(a))continue;const o={deliveryId:n.deliveryId,claimedLease:t,completion:Promise.resolve()};o.completion=this.handleRequest(n,t,e,e=>{o.cancel=e}).catch(()=>{}).finally(()=>{this.workers.delete(a)}),this.workers.set(a,o)}}}finally{t&&clearInterval(t),await Promise.allSettled([...this.workers.values()].map(e=>e.completion))}}async renewActive(){const e=this.getLease();if(!e)return;const t=[];for(const a of this.workers.values())sameLeaseBinding(a.claimedLease,e)?t.push(a.deliveryId):a.cancel?.();if(!t.length)return;const a=await this.channel.renew({deliveryIds:t,lease:e});if(a.notPending.length){const e=new Set(a.notPending);for(const t of this.workers.values())e.has(t.deliveryId)&&t.cancel?.()}}async handleCancel(e,t){const a=validateRuntimeCancelEnvelope(e.message);if(!a.ok)return void await this.handlePoison(e,`invalid_cancel:${a.reason}`,t);const n=a.envelope.payload,o=await this.channel.history({operationId:n.operationId,lease:t});if("found"===o.status&&o.snapshot.terminal&&"final_message"===o.snapshot.terminalSource)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});if("not_found"===o.status||"expired"===o.status)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});const i=o.snapshot.request.payload;i.operationId===n.operationId&&i.requestMessageId===n.requestMessageId&&i.requestHash===n.requestHash&&i.dispatchEpoch===n.dispatchEpoch&&i.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,a,n){const o=validateRuntimeRequestEnvelope(e.message);if(!o.ok)return void await this.handlePoison(e,`invalid_request:${o.reason}`,t);const i=o.envelope.payload,r=e.executionGrant,s=new AbortController;n(()=>s.abort());const d=async()=>await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:this.currentLease(t)}),c=await this.channel.history({operationId:i.operationId,lease:t});if("not_found"===c.status||"expired"===c.status)return void await d();if(c.snapshot.terminal&&"final_message"===c.snapshot.terminalSource)return void await d();if(this.options.allowedMethods&&!this.options.allowedMethods.has(i.method))return await this.reject(i,"unsupported_method",t),void await d();if("deadline"===c.snapshot.terminalSource||Date.parse(i.executionDeadlineAt)<=Date.now()||!c.snapshot.started&&Date.parse(i.claimDeadlineAt)<=Date.now())return await this.reject(i,"execution_deadline_exceeded",t),void await d();const l=await this.journal.getCancellation(i.operationId),u=c.snapshot.cancelRequested||null!==l;if(u&&"queued"===c.snapshot.status)return await this.reject(i,"cancelled_before_dispatch",t),void await d();if(i.instanceId!==t.instanceId||i.runtimeHandlerIdentity!==t.handlerIdentity||i.dispatchEpoch!==t.runtimeEpoch)return await this.reject(i,"authority_mismatch",t),void await d();const p=c.snapshot.request.payload;if(c.snapshot.operationId!==i.operationId||c.snapshot.requestMessageId!==i.requestMessageId||c.snapshot.requestHash!==i.requestHash||!semanticEqual(p,i))return void await this.handlePoison(e,"request_history_mismatch",t);const m=c.snapshot.started?.payload,h=void 0!==m&&m.operationId===i.operationId&&m.requestMessageId===i.requestMessageId&&m.requestHash===i.requestHash&&m.executionGrantHash===i.executionGrantHash&&m.handlerIdentity===t.handlerIdentity&&m.dispatchEpoch===t.runtimeEpoch&&m.method===i.method,I=await this.journal.get(i.operationId),E=await this.journal.acceptMessageRequest({operationId:i.operationId,command:i.command.name,paramsHash:i.paramsHash,runtimeEpoch:i.dispatchEpoch,requestMessageId:i.requestMessageId,requestHash:i.requestHash}),f=h?I:E,_=decideMessageServiceRecovery(c.snapshot,i,f,h);if("already_terminal"===_)return void await d();if(!CORE_RECOVERY_DECISIONS.has(_))return await this.markOutcomeUnknownIfMutable(E),await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(i,new RuntimeCommandError("OUTCOME_UNKNOWN",`Types recovery policy requires ${_}; this runtime cannot safely replay Core`),(new Date).toISOString())}),void await d();if(await this.verifier.refresh(),!r)return h?await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(i,new RuntimeCommandError("OUTCOME_UNKNOWN","retained started proof has no live execution grant"),(new Date).toISOString())}):await this.reject(i,"invalid_execution_grant",t),void await d();this.verifier.verifyAndDecodeGrant(r)||await this.verifier.refresh(!0);const R=()=>s.abort();let M;a.addEventListener("abort",R,{once:!0});try{M=await verifyAndCreateMessageServiceCoreExecutionAttempt({request:i,executionGrant:r,runtimeEpoch:t.runtimeEpoch,lease:t,verifier:this.verifier,allowExpiredGrant:h,abortSignal:s.signal,resolvePorts:async()=>await this.ports.resolve({request:i,executionGrant:r,command:i.command,getLease:()=>this.currentLease(t),journalRecord:E,abortSignal:s.signal})})}catch(e){if(a.removeEventListener("abort",R),s.abort(),!(e instanceof RuntimeResourceResolutionError))throw e;if("transient"===e.kind)return;return h?(await this.markOutcomeUnknownIfMutable(E),await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(i,new RuntimeCommandError("OUTCOME_UNKNOWN","eager resources cannot be reconstructed after retained started proof"),(new Date).toISOString())})):await this.reject(i,"integrity_mismatch"===e.kind?"resource_integrity_mismatch":"resource_resolution_rejected",t),void await d()}if(!M.ok)return a.removeEventListener("abort",R),s.abort(),h?(await this.markOutcomeUnknownIfMutable(E),await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(i,new RuntimeCommandError("OUTCOME_UNKNOWN","execution grant cannot be revalidated after retained started proof"),(new Date).toISOString())})):await this.reject(i,"invalid_execution_grant",t),void await d();if(!u||"prepared"!==E.phase&&"started"!==E.phase||s.abort(),s.signal.aborted&&!h)return a.removeEventListener("abort",R),await this.reject(i,"cancelled_before_dispatch",t),void await d();h||await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),executionGrant:r,type:RUNTIME_MESSAGE_TYPES.started,payload:{...runtimeMessageBase(i),executionMode:"start",executionGrantHash:i.executionGrantHash,handlerIdentity:t.handlerIdentity,claimedAt:E.messageBinding?.acceptedAt??E.updatedAt}});const w=this.dispatcher.dispatch(i.command,M.context);w.completion.catch(()=>{}),n(()=>{s.abort(),w.cancel()}),s.signal.aborted&&w.cancel();const g=this.forwardProgress(i,w.progress,t).catch(()=>{});let v,y=!1;try{if(("mcp/set"===i.method||"mcp/unset"===i.method)&&hasEffectReceiptHandoff(w)){const e=await w.effectCommitted;if(e){if(e.operationId!==i.operationId||e.method!==i.command.name)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core MCP receipt correlation mismatch");const a=runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:i.method,result:e.result}),n={schemaVersion:1,operationId:i.operationId,method:i.method,coreResultRef:`sha256:${a}`,coreResultHash:a};v=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,n),y=!0,await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(i),effectRecord:n,effectRecordHash:v,committedAt:e.committedAt}}),await w.acknowledgeEffectReceipt({operationId:i.operationId,method:i.command.name,effectRecordHash:v})}}const e=await w.completion;await g;const a=await this.journal.get(i.operationId);if(!a||!isCommittedCorePhase(a.phase))throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core completed without a committed local journal record");const n=a.updatedAt;let o,r;if("query"!==RUNTIME_INVOCATION_RECOVERY_CLASSES[i.method]){const r=isAgentProjectionMethod(i.method),s=w.getSnapshot().projection,c=a.agentProjectionIntent??a.agentProjectionPreparationFailure;if(r&&!c)throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent effect committed without an atomic projection preparation record");if(!v){const o=c?.coreResultHash??runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:i.method,result:e}),r={schemaVersion:1,operationId:i.operationId,method:i.method,coreResultRef:`sha256:${o}`,coreResultHash:o,...a.agentProjectionIntent?{projectionRevision:a.agentProjectionIntent.projectionRevision}:{}};v=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,r),y=!0,await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(i),effectRecord:r,effectRecordHash:v,committedAt:n}})}const l=r?await awaitRuntimeProjection(w):s;if(r&&a.agentProjectionIntent&&(!l||l.revision!==a.agentProjectionIntent.projectionRevision))throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent projection receipt revision mismatch");if("pending"===l?.state)return void await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionPending,payload:{...runtimeMessageBase(i),revision:l.revision}});if("synced"===l?.state&&await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionSynced,payload:{...runtimeMessageBase(i),revision:l.revision,...l.platformAgentId?{platformAgentId:l.platformAgentId}:{}}}),"blocked"===l?.state){await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionBlocked,payload:{...runtimeMessageBase(i),revision:l.revision,errorCode:"PROJECTION_BLOCKED"}});const e=RUNTIME_ERROR_DESCRIPTORS.PROJECTION_BLOCKED;return await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:{...runtimeMessageBase(i),outcome:"projection_blocked",response:{jsonrpc:"2.0",id:i.request.id,error:{code:e.jsonRpcCode,message:"PROJECTION_BLOCKED",data:{code:"PROJECTION_BLOCKED",operationId:i.operationId,effectState:"committed",retryMode:e.retryMode}}},effectRecordHash:v,completedAt:n}}),void await d()}o=r?l?.platformAgentId:void 0}try{r=sanitizeRuntimeServiceResult(i.method,e,M.context,o)}catch(e){throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed Core result cannot be mapped safely",void 0,{cause:e})}const s=runtimeMessageHash(RUNTIME_HASH_DOMAINS.serviceResult,{method:i.method,result:r});y=!0;const c=await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:{...runtimeMessageBase(i),outcome:"succeeded",response:{jsonrpc:"2.0",id:i.request.id,result:r},serviceResultHash:s,...v?{effectRecordHash:v}:{},completedAt:n}});if(("mcp/set"===i.method||"mcp/unset"===i.method)&&hasEffectReceiptHandoff(w)&&"function"==typeof w.acknowledgeTerminalReceipt){if(!v)throw new RuntimeCommandError("OUTCOME_UNKNOWN","MCP terminal response lacks its committed effect hash");await w.acknowledgeTerminalReceipt({operationId:i.operationId,method:i.command.name,effectRecordHash:v,terminalMessageId:c.message.message_id})}await d()}catch(e){const a=await this.journal.get(i.operationId);if(a&&isCommittedCorePhase(a.phase)&&y)throw e;const n=!a||!isCommittedCorePhase(a.phase)||e instanceof RuntimeCommandError&&"OUTCOME_UNKNOWN"===e.code?e:new RuntimeCommandError("OUTCOME_UNKNOWN","Core effect committed before terminal mapping",void 0,{cause:e}),o=failurePayload(i,n,a?.updatedAt??(new Date).toISOString());await this.channel.append({operationId:i.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:o}),await d()}finally{s.abort(),a.removeEventListener("abort",R)}}async handlePoison(e,t,a){this.currentLease(a);(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(a)})}async forwardProgress(e,t,a){for await(const n of t)await this.channel.append({operationId:e.operationId,lease:this.currentLease(a),type:RUNTIME_MESSAGE_TYPES.progress,payload:{...runtimeMessageBase(e),phase:n.phase,progress:n}})}async reject(e,t,a){await this.channel.append({operationId:e.operationId,lease:this.currentLease(a),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"})}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,a,n){const o=RUNTIME_INVOCATION_PARITY_ENTRIES.find(e=>e.acpMethod===t.method),i=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",r="failed"===a?.phase||"outcome_unknown"===a?.phase?"terminal":a?.phase,s=a&&a.runtimeEpoch===t.dispatchEpoch&&(void 0===a.messageBinding||a.messageBinding.requestMessageId===t.requestMessageId&&a.messageBinding.requestHash===t.requestHash)?"continuous":"discontinuous";return reduceRuntimeRecoveryPolicy({recoveryClass:o?.recoveryClass??RUNTIME_INVOCATION_RECOVERY_CLASSES[t.method],projectionRequired:"mcp/set"===t.method||"mcp/unset"===t.method||(o?.projectionRequired??!1),messageServicePhase:i,executionBoundaryCrossed:n,lineageContinuity:s,journalState:a?"available":"unavailable",...r?{journalPhase:r}:{}})}export function sanitizeRuntimeServiceResult(e,t,a,n){if("session/clear"===e)return serviceClearResult(t,a?.historyBoundary);if("session/set_model"===e)return serviceSetModelResult(t,a);if(!(e in SERVICE_CANONICAL_COMMAND_NAMES))return t;const o="cron/status"===e&&record(t)?{enabled:t.enabled,jobCount:t.jobCount,..."number"==typeof t.nextWakeAtMs?{nextWakeAtMs:t.nextWakeAtMs}:{}}:t;return mapCanonicalResultToService(e,o,{...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 a=t;if(!a.committedIntentId)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Message Service clear receipt is unavailable");return{...e,clearIntentId:a.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 a=t.verifiedMessageGrant?.claims.command;if(!a||"session.setModel"!==a.name||"conversation"!==a.context.scope)throw new RuntimeCommandError("OUTCOME_UNKNOWN","set_model Service target receipt is unavailable");const n=a.params.modelOverrideId;return{conversationId:a.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 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,a){const n=t instanceof RuntimeCommandError?t.code:"RUNTIME_EXECUTION_FAILED",o=n in RUNTIME_ERROR_DESCRIPTORS?n:"RUNTIME_EXECUTION_FAILED",i="OUTCOME_UNKNOWN"===o||"INSTANCE_EPOCH_MISMATCH"===o?"outcome_unknown":"COMMAND_EXPIRED"===o?"expired":"OPERATION_CANCELLED"===o?"cancelled":"failed",r="outcome_unknown"===i?"OUTCOME_UNKNOWN":o,s=RUNTIME_ERROR_DESCRIPTORS[r],d="outcome_unknown"===i?"outcome_unknown":i;return{...runtimeMessageBase(e),outcome:i,response:{jsonrpc:"2.0",id:e.request.id,error:{code:s.jsonRpcCode,message:r,data:{code:r,operationId:e.operationId,effectState:d,retryMode:s.retryMode}}},completedAt:a}}function prestartFailurePayload(e,t){const a="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[a in RUNTIME_ERROR_DESCRIPTORS?a:"RUNTIME_EXECUTION_FAILED"],o="COMMAND_EXPIRED"===a?"expired":"OPERATION_CANCELLED"===a?"cancelled":"failed";return{...runtimeMessageBase(e),outcome:o,response:{jsonrpc:"2.0",id:e.request.id,error:{code:n.jsonRpcCode,message:a,data:{code:a,operationId:e.operationId,effectState:o,retryMode:n.retryMode}}},completedAt:(new Date).toISOString()}}function abortable(e,t){return t?t.aborted?Promise.resolve():new Promise((a,n)=>{const o=()=>a();t.addEventListener("abort",o,{once:!0}),e.then(()=>{t.removeEventListener("abort",o),a()},e=>{t.removeEventListener("abort",o),n(e)})}):e}function delay(e,t){return new Promise(a=>{if(t?.aborted)return a();const n=()=>{clearTimeout(o),a()},o=setTimeout(()=>{t?.removeEventListener("abort",n),a()},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{RUNTIME_MESSAGE_TYPES,runtimeMessageBase,runtimeMessageHash,semanticEqual,validateRuntimeCancelEnvelope,validateRuntimeRequestEnvelope}from"./runtime-message-channel-contract.js";import{RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";export class MsRuntimeMessageChannelAdapter{dispatcher;channel;verifier;ports;journal;getLease;options;controller;running;workers=new Map;poisonJournal;receiptHistory=this;constructor(e,t,a,o,n,r,i){if(this.dispatcher=e,this.channel=t,this.verifier=a,this.ports=o,this.journal=n,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;this.controller=new AbortController;const e=this.loop(this.controller.signal).finally(()=>{this.running===e&&(this.running=void 0)});this.running=e}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(()=>{})},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 a;try{a=await this.channel.read({maxCount:this.options.readCount??32,blockMs:this.options.blockMs??5e3,lease:t,signal:e})}catch{if(e.aborted)return;await delay(this.options.idleDelayMs??500,e);continue}for(const o of a){if(e.aborted)break;if(o.message.type===RUNTIME_MESSAGE_TYPES.cancel){await this.handleCancel(o,t).catch(e=>this.observeDeliveryError(e));continue}const a=o.message.channel_id;if(this.workers.has(a))continue;const n={deliveryId:o.deliveryId,claimedLease:t,completion:Promise.resolve()};n.completion=this.handleRequest(o,t,e,e=>{n.cancel=e}).catch(e=>this.observeDeliveryError(e)).finally(()=>{this.workers.delete(a)}),this.workers.set(a,n)}}}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 a of this.workers.values())sameLeaseBinding(a.claimedLease,e)?t.push(a.deliveryId):a.cancel?.();if(!t.length)return;const a=await this.channel.renew({deliveryIds:t,lease:e});if(a.notPending.length){const e=new Set(a.notPending);for(const t of this.workers.values())e.has(t.deliveryId)&&t.cancel?.()}}async handleCancel(e,t){const a=validateRuntimeCancelEnvelope(e.message);if(!a.ok)return void await this.handlePoison(e,`invalid_cancel:${a.reason}`,t);const o=a.envelope.payload,n=await this.channel.history({operationId:o.operationId,lease:t});if("found"===n.status&&n.snapshot.terminal&&"final_message"===n.snapshot.terminalSource)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});if("not_found"===n.status||"expired"===n.status)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});const r=n.snapshot.request.payload;r.operationId===o.operationId&&r.requestMessageId===o.requestMessageId&&r.requestHash===o.requestHash&&r.dispatchEpoch===o.dispatchEpoch&&r.method===o.method?(await this.journal.observeCancellation({operationId:o.operationId,requestMessageId:o.requestMessageId,requestHash:o.requestHash,eventId:o.eventId}),this.workers.get(o.operationId)?.cancel?.(),await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t})):await this.handlePoison(e,"cancel_history_mismatch",t)}async handleRequest(e,t,a,o){const n=validateRuntimeRequestEnvelope(e.message);if(!n.ok)return void await this.handlePoison(e,`invalid_request:${n.reason}`,t);const r=n.envelope.payload,i=e.executionGrant,s=new AbortController;o(()=>s.abort());const d=async()=>await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:this.currentLease(t)}),c=await this.channel.history({operationId:r.operationId,lease:t});if("not_found"===c.status||"expired"===c.status)return void await d();if(c.snapshot.terminal&&"final_message"===c.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"===c.snapshot.terminalSource||Date.parse(r.executionDeadlineAt)<=Date.now()||!c.snapshot.started&&Date.parse(r.claimDeadlineAt)<=Date.now())return await this.reject(r,"execution_deadline_exceeded",t),void await d();const l=await this.journal.getCancellation(r.operationId),u=c.snapshot.cancelRequested||null!==l;if(u&&"queued"===c.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 p=c.snapshot.request.payload;if(c.snapshot.operationId!==r.operationId||c.snapshot.requestMessageId!==r.requestMessageId||c.snapshot.requestHash!==r.requestHash||!semanticEqual(p,r))return void await this.handlePoison(e,"request_history_mismatch",t);const m=c.snapshot.started?.payload,h=void 0!==m&&m.operationId===r.operationId&&m.requestMessageId===r.requestMessageId&&m.requestHash===r.requestHash&&m.executionGrantHash===r.executionGrantHash&&m.handlerIdentity===t.handlerIdentity&&m.dispatchEpoch===t.runtimeEpoch&&m.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=h?I:E,_=decideMessageServiceRecovery(c.snapshot,r,f,h);if("already_terminal"===_)return void await d();if(!CORE_RECOVERY_DECISIONS.has(_))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 ${_}; this runtime cannot safely replay Core`),(new Date).toISOString())}),void await d();if(await this.verifier.refresh(),!i)return h?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 R=()=>s.abort();let v;a.addEventListener("abort",R,{once:!0});try{v=await verifyAndCreateMessageServiceCoreExecutionAttempt({request:r,executionGrant:i,runtimeEpoch:t.runtimeEpoch,lease:t,verifier:this.verifier,allowExpiredGrant:h,abortSignal:s.signal,resolvePorts:async()=>await this.ports.resolve({request:r,executionGrant:i,command:r.command,getLease:()=>this.currentLease(t),journalRecord:E,abortSignal:s.signal})})}catch(e){if(a.removeEventListener("abort",R),s.abort(),!(e instanceof RuntimeResourceResolutionError))throw e;if("transient"===e.kind)return;return h?(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 d()}if(!v.ok)return a.removeEventListener("abort",R),s.abort(),h?(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 d();if(!u||"prepared"!==E.phase&&"started"!==E.phase||s.abort(),s.signal.aborted&&!h)return a.removeEventListener("abort",R),await this.reject(r,"cancelled_before_dispatch",t),void await d();h||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 M=this.dispatcher.dispatch(r.command,v.context);M.completion.catch(()=>{}),o(()=>{s.abort(),M.cancel()}),s.signal.aborted&&M.cancel();const g=this.forwardProgress(r,M.progress,t).catch(()=>{});let w,y=!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 a=runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:r.method,result:e.result}),o={schemaVersion:1,operationId:r.operationId,method:r.method,coreResultRef:`sha256:${a}`,coreResultHash:a};w=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,o),y=!0,await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(r),effectRecord:o,effectRecordHash:w,committedAt:e.committedAt}}),await M.acknowledgeEffectReceipt({operationId:r.operationId,method:r.command.name,effectRecordHash:w})}}const a=await M.completion;await g;const o=await this.journal.get(r.operationId);if(!o||!isCommittedCorePhase(o.phase))throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core completed without a committed local journal record");const n=o.updatedAt;let i,s;if("query"!==RUNTIME_INVOCATION_RECOVERY_CLASSES[r.method]){const e=isAgentProjectionMethod(r.method),s=M.getSnapshot().projection,c=o.agentProjectionIntent??o.agentProjectionPreparationFailure;if(e&&!c)throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent effect committed without an atomic projection preparation record");if(!w){const e=c?.coreResultHash??runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:r.method,result:a}),i={schemaVersion:1,operationId:r.operationId,method:r.method,coreResultRef:`sha256:${e}`,coreResultHash:e,...o.agentProjectionIntent?{projectionRevision:o.agentProjectionIntent.projectionRevision}:{}};w=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,i),y=!0,await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(r),effectRecord:i,effectRecordHash:w,committedAt:n}})}const l=e?await awaitRuntimeProjection(M):s;if(e&&o.agentProjectionIntent&&(!l||l.revision!==o.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.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionSynced,payload:{...runtimeMessageBase(r),revision:l.revision,...l.platformAgentId?{platformAgentId:l.platformAgentId}:{}}}),"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:n}}),void await d()}i=e?l?.platformAgentId:void 0}try{s=sanitizeRuntimeServiceResult(r.method,a,v.context,i)}catch(e){throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed Core result cannot be mapped safely",void 0,{cause:e})}const c=runtimeMessageHash(RUNTIME_HASH_DOMAINS.serviceResult,{method:r.method,result:s});y=!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:c,...w?{effectRecordHash:w}:{},completedAt:n}});if(this.observeRequestTerminalAccepted(e.deliveryId,l.message),("mcp/set"===r.method||"mcp/unset"===r.method)&&hasEffectReceiptHandoff(M)&&"function"==typeof M.acknowledgeTerminalReceipt){if(!w)throw new RuntimeCommandError("OUTCOME_UNKNOWN","MCP terminal response lacks its committed effect hash");await M.acknowledgeTerminalReceipt({operationId:r.operationId,method:r.command.name,effectRecordHash:w,terminalMessageId:l.message.message_id})}await d(),this.observeRequestAcknowledged(e.deliveryId,l.message)}catch(e){const a=await this.journal.get(r.operationId);if(a&&isCommittedCorePhase(a.phase)&&y)throw e;const o=!a||!isCommittedCorePhase(a.phase)||e instanceof RuntimeCommandError&&"OUTCOME_UNKNOWN"===e.code?e:new RuntimeCommandError("OUTCOME_UNKNOWN","Core effect committed before terminal mapping",void 0,{cause:e}),n=failurePayload(r,o,a?.updatedAt??(new Date).toISOString());await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:n}),await d()}finally{s.abort(),a.removeEventListener("abort",R)}}async handlePoison(e,t,a){this.currentLease(a);(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(a)})}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,a){for await(const o of t)await this.channel.append({operationId:e.operationId,lease:this.currentLease(a),type:RUNTIME_MESSAGE_TYPES.progress,payload:{...runtimeMessageBase(e),phase:o.phase,progress:o}})}async reject(e,t,a){await this.channel.append({operationId:e.operationId,lease:this.currentLease(a),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"})}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,a,o){const n=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":o?"running":"queued",i="failed"===a?.phase||"outcome_unknown"===a?.phase?"terminal":a?.phase,s=a&&a.runtimeEpoch===t.dispatchEpoch&&(void 0===a.messageBinding||a.messageBinding.requestMessageId===t.requestMessageId&&a.messageBinding.requestHash===t.requestHash)?"continuous":"discontinuous";return reduceRuntimeRecoveryPolicy({recoveryClass:n?.recoveryClass??RUNTIME_INVOCATION_RECOVERY_CLASSES[t.method],projectionRequired:"mcp/set"===t.method||"mcp/unset"===t.method||(n?.projectionRequired??!1),messageServicePhase:r,executionBoundaryCrossed:o,lineageContinuity:s,journalState:a?"available":"unavailable",...i?{journalPhase:i}:{}})}export function sanitizeRuntimeServiceResult(e,t,a,o){if("session/clear"===e)return serviceClearResult(t,a?.historyBoundary);if("session/set_model"===e)return serviceSetModelResult(t,a);if(!(e in SERVICE_CANONICAL_COMMAND_NAMES))return t;const n="cron/status"===e&&record(t)?{enabled:t.enabled,jobCount:t.jobCount,..."number"==typeof t.nextWakeAtMs?{nextWakeAtMs:t.nextWakeAtMs}:{}}:t;return mapCanonicalResultToService(e,n,{...o?{platformAgentId:o}:{},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 a=t;if(!a.committedIntentId)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Message Service clear receipt is unavailable");return{...e,clearIntentId:a.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 a=t.verifiedMessageGrant?.claims.command;if(!a||"session.setModel"!==a.name||"conversation"!==a.context.scope)throw new RuntimeCommandError("OUTCOME_UNKNOWN","set_model Service target receipt is unavailable");const o=a.params.modelOverrideId;return{conversationId:a.context.conversation.runtimeConversationUid,...null===o?{defaultModelId:e.effectiveModelId}:{},modelOverrideId:o,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 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,a){const o=t instanceof RuntimeCommandError?t.code:"RUNTIME_EXECUTION_FAILED",n=o in RUNTIME_ERROR_DESCRIPTORS?o:"RUNTIME_EXECUTION_FAILED",r="OUTCOME_UNKNOWN"===n||"INSTANCE_EPOCH_MISMATCH"===n?"outcome_unknown":"COMMAND_EXPIRED"===n?"expired":"OPERATION_CANCELLED"===n?"cancelled":"failed",i="outcome_unknown"===r?"OUTCOME_UNKNOWN":n,s=RUNTIME_ERROR_DESCRIPTORS[i],d="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:d,retryMode:s.retryMode}}},completedAt:a}}function prestartFailurePayload(e,t){const a="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",o=RUNTIME_ERROR_DESCRIPTORS[a in RUNTIME_ERROR_DESCRIPTORS?a:"RUNTIME_EXECUTION_FAILED"],n="COMMAND_EXPIRED"===a?"expired":"OPERATION_CANCELLED"===a?"cancelled":"failed";return{...runtimeMessageBase(e),outcome:n,response:{jsonrpc:"2.0",id:e.request.id,error:{code:o.jsonRpcCode,message:a,data:{code:a,operationId:e.operationId,effectState:n,retryMode:o.retryMode}}},completedAt:(new Date).toISOString()}}function abortable(e,t){return t?t.aborted?Promise.resolve():new Promise((a,o)=>{const n=()=>a();t.addEventListener("abort",n,{once:!0}),e.then(()=>{t.removeEventListener("abort",n),a()},e=>{t.removeEventListener("abort",n),o(e)})}):e}function delay(e,t){return new Promise(a=>{if(t?.aborted)return a();const o=()=>{clearTimeout(n),a()},n=setTimeout(()=>{t?.removeEventListener("abort",o),a()},e);t?.addEventListener("abort",o,{once:!0})})}
@@ -1 +1 @@
1
- import{createHash}from"node:crypto";import{CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES,RUNTIME_HASH_DOMAINS,RUNTIME_INVOCATION_RECOVERY_CLASSES,RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION,SERVICE_CANONICAL_COMMAND_NAMES,canonicalizeJcs,runtimeIdempotencyFingerprintPayload,runtimeHashPreimage,validateRuntimeRpcRequestResourceBindings}from"@beeos-ai/beeos-types/runtime";import{sessionSetModelMappingMatches}from"./session-set-model-mapping.js";export const RUNTIME_MESSAGE_TYPES=Object.freeze({request:"runtime_rpc_request",started:"runtime_operation_started",progress:"runtime_operation_progress",effectCommitted:"runtime_effect_committed",final:"runtime_rpc_response",projectionPending:"runtime_projection_pending",projectionSynced:"runtime_projection_synced",projectionBlocked:"runtime_projection_blocked",cancel:"runtime_operation_cancel_requested"});const methods=new Set(Object.keys(RUNTIME_INVOCATION_RECOVERY_CLASSES)),commandNames={...SERVICE_CANONICAL_COMMAND_NAMES,...CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES},sha=/^[0-9a-f]{64}$/,decimal=/^(0|[1-9][0-9]*)$/;export function validateRuntimeRequestEnvelope(e){if(!record(e)||e.type!==RUNTIME_MESSAGE_TYPES.request||!record(e.payload))return{ok:!1,reason:"delivery is not a runtime request"};const t=e,n=t.payload;if(!exactKeys(t,["message_id","offset","channel_id","type","payload","publisher_id","idempotency_key","created_at","state",...void 0===t.in_reply_to?[]:["in_reply_to"]])||void 0!==t.in_reply_to||"completed"!==t.state||!nonempty(t.message_id)||!nonempty(t.channel_id)||!Number.isInteger(t.offset)||t.offset<1||!nonempty(t.publisher_id)||!nonempty(t.idempotency_key))return{ok:!1,reason:"invalid persisted envelope"};const r=void 0!==n.resourceBindingContractRevision,a=void 0!==n.resourceBindings;if(!exactKeys(n,["schemaVersion","operationId","requestMessageId","requestHash","paramsHash","commandHash","idempotencyKeyHash","idempotencyFingerprint","callerIntentHash","callerUserId","callerIdentityId","instanceId","runtimeHandlerIdentity","dispatchEpoch","method","request","command","executionGrantHash","claimDeadlineAt","executionDeadlineAt","createdAt",...r||a?["resourceBindingContractRevision","resourceBindings"]:[]]))return{ok:!1,reason:"request payload fields do not match v1.3"};if(r!==a||r&&n.resourceBindingContractRevision!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||a&&!Array.isArray(n.resourceBindings))return{ok:!1,reason:"resource binding carrier is invalid"};if(!(1===n.schemaVersion&&methods.has(n.method)&&decimal.test(n.dispatchEpoch)&&[n.operationId,n.requestMessageId,n.callerUserId,n.callerIdentityId,n.instanceId,n.runtimeHandlerIdentity].every(nonempty)&&[n.requestHash,n.paramsHash,n.commandHash,n.idempotencyKeyHash,n.idempotencyFingerprint,n.callerIntentHash,n.executionGrantHash].every(e=>"string"==typeof e&&sha.test(e))&&record(n.request)&&record(n.command)&&"2.0"===n.request.jsonrpc&&n.request.method===n.method&&n.command.name===commandNames[n.method]))return{ok:!1,reason:"invalid request union"};if(!sessionSetModelMappingMatches(n.request,n.command))return{ok:!1,reason:"request command mapping mismatch"};if(t.channel_id!==n.operationId||t.message_id!==n.requestMessageId||n.command.context?.operationId!==n.operationId||n.command.context?.runtimeInstanceUid!==n.instanceId||n.command.context?.dispatchEpoch!==n.dispatchEpoch||n.command.context?.paramsHash!==n.paramsHash||n.command.context?.idempotencyKeyHash!==n.idempotencyKeyHash||n.command.context?.claimDeadline!==n.claimDeadlineAt||n.command.context?.executionDeadline!==n.executionDeadlineAt)return{ok:!1,reason:"request correlation mismatch"};try{const e={callerUserId:n.callerUserId,instanceId:n.instanceId,method:n.method,params:n.request.params};if(digest(RUNTIME_HASH_DOMAINS.request,n.request)!==n.requestHash||digest(RUNTIME_HASH_DOMAINS.params,{method:n.method,params:n.request.params})!==n.paramsHash||digest(RUNTIME_HASH_DOMAINS.command,{command:n.command})!==n.commandHash||digest(RUNTIME_HASH_DOMAINS.callerIntent,e)!==n.callerIntentHash||digest(RUNTIME_HASH_DOMAINS.idempotencyFingerprint,runtimeIdempotencyFingerprintPayload(n.callerUserId,n.instanceId,n.method,n.callerIntentHash))!==n.idempotencyFingerprint)return{ok:!1,reason:"canonical hash mismatch"}}catch{return{ok:!1,reason:"canonical hash mismatch"}}if(![n.claimDeadlineAt,n.executionDeadlineAt,n.createdAt].every(e=>"string"==typeof e&&Number.isFinite(Date.parse(e))))return{ok:!1,reason:"invalid request timestamp"};if(r){let e;try{e=validateRuntimeRpcRequestResourceBindings({type:"runtime_rpc_request",operationId:n.operationId,requestMessageId:n.requestMessageId,payload:n},Math.floor(Date.now()/1e3))}catch{return{ok:!1,reason:"resource binding validation failed"}}if(e.length)return{ok:!1,reason:"resource binding validation failed"}}return{ok:!0,envelope:t}}export function validateRuntimeCancelEnvelope(e){if(!record(e)||e.type!==RUNTIME_MESSAGE_TYPES.cancel||!record(e.payload))return{ok:!1,reason:"delivery is not a runtime cancel"};const t=e,n=t.payload;return exactKeys(n,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","eventId","requestedBy",...void 0===n.reason?[]:["reason"]])&&exactKeys(t,["message_id","offset","channel_id","type","payload","publisher_id","in_reply_to","idempotency_key","created_at","state"])&&"completed"===t.state&&nonempty(t.message_id)&&Number.isInteger(t.offset)&&!(t.offset<1)&&nonempty(t.publisher_id)&&nonempty(t.idempotency_key)&&1===n.schemaVersion&&methods.has(n.method)&&decimal.test(n.dispatchEpoch)&&sha.test(n.requestHash)&&t.channel_id===n.operationId&&t.in_reply_to===n.requestMessageId&&nonempty(n.eventId)&&nonempty(n.requestedBy)&&(void 0===n.reason||"string"==typeof n.reason)?{ok:!0,envelope:t}:{ok:!1,reason:"invalid cancel correlation"}}export function runtimeMessageBase(e){return{schemaVersion:1,operationId:e.operationId,requestMessageId:e.requestMessageId,requestHash:e.requestHash,dispatchEpoch:e.dispatchEpoch,method:e.method}}export function runtimeMessageHash(e,t){return digest(e,t)}function digest(e,t){return createHash("sha256").update(runtimeHashPreimage(e,t)).digest("hex")}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function nonempty(e){return"string"==typeof e&&e.length>0}function exactKeys(e,t){const n=Object.keys(e).sort(),r=[...t].sort();return n.length===r.length&&n.every((e,t)=>e===r[t])}export function semanticEqual(e,t){return canonicalizeJcs(e)===canonicalizeJcs(t)}
1
+ import{createHash}from"node:crypto";import{CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES,RUNTIME_HASH_DOMAINS,RUNTIME_INVOCATION_RECOVERY_CLASSES,RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION,SERVICE_CANONICAL_COMMAND_NAMES,canonicalizeJcs,runtimeIdempotencyFingerprintPayload,runtimeHashPreimage,validateRuntimeRpcRequestResourceBindings}from"@beeos-ai/beeos-types/runtime";import{sessionSetModelMappingMatches}from"./session-set-model-mapping.js";export const RUNTIME_MESSAGE_TYPES=Object.freeze({request:"runtime_rpc_request",started:"runtime_operation_started",progress:"runtime_operation_progress",effectCommitted:"runtime_effect_committed",final:"runtime_rpc_response",projectionPending:"runtime_projection_pending",projectionSynced:"runtime_projection_synced",projectionBlocked:"runtime_projection_blocked",cancel:"runtime_operation_cancel_requested"});const methods=new Set(Object.keys(RUNTIME_INVOCATION_RECOVERY_CLASSES)),commandNames={...SERVICE_CANONICAL_COMMAND_NAMES,...CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES},sha=/^[0-9a-f]{64}$/,decimal=/^(0|[1-9][0-9]*)$/;export function validateRuntimeRequestEnvelope(e){if(!record(e)||e.type!==RUNTIME_MESSAGE_TYPES.request||!record(e.payload))return{ok:!1,reason:"delivery is not a runtime request"};const t=e,n=t.payload;if(!exactKeys(t,["message_id","offset","channel_id","type","payload","publisher_id","idempotency_key","created_at","state",...void 0===t.in_reply_to?[]:["in_reply_to"]])||void 0!==t.in_reply_to||"completed"!==t.state||!nonempty(t.message_id)||!nonempty(t.channel_id)||!Number.isInteger(t.offset)||t.offset<1||!nonempty(t.publisher_id)||!nonempty(t.idempotency_key))return{ok:!1,reason:"invalid persisted envelope"};const r=void 0!==n.resourceBindingContractRevision,a=void 0!==n.resourceBindings;if(!exactKeys(n,["schemaVersion","operationId","requestMessageId","requestHash","paramsHash","commandHash","idempotencyKeyHash","idempotencyFingerprint","callerIntentHash","callerUserId","callerIdentityId","instanceId","runtimeHandlerIdentity","dispatchEpoch","method","request","command","executionGrantHash","claimDeadlineAt","executionDeadlineAt","createdAt",...r||a?["resourceBindingContractRevision","resourceBindings"]:[]]))return{ok:!1,reason:"request payload fields do not match v1.3"};if(r!==a||r&&n.resourceBindingContractRevision!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||a&&!Array.isArray(n.resourceBindings))return{ok:!1,reason:"resource binding carrier is invalid"};if(!(1===n.schemaVersion&&methods.has(n.method)&&decimal.test(n.dispatchEpoch)&&[n.operationId,n.requestMessageId,n.callerUserId,n.callerIdentityId,n.instanceId,n.runtimeHandlerIdentity].every(nonempty)&&[n.requestHash,n.paramsHash,n.commandHash,n.idempotencyKeyHash,n.idempotencyFingerprint,n.callerIntentHash,n.executionGrantHash].every(e=>"string"==typeof e&&sha.test(e))&&record(n.request)&&record(n.command)&&"2.0"===n.request.jsonrpc&&n.request.method===n.method&&n.command.name===commandNames[n.method]))return{ok:!1,reason:"invalid request union"};if(!sessionSetModelMappingMatches(n.request,n.command))return{ok:!1,reason:"request command mapping mismatch"};if(t.channel_id!==n.operationId||t.message_id!==n.requestMessageId||n.command.context?.operationId!==n.operationId||n.command.context?.runtimeInstanceUid!==n.instanceId||n.command.context?.dispatchEpoch!==n.dispatchEpoch||n.command.context?.paramsHash!==n.paramsHash||n.command.context?.idempotencyKeyHash!==n.idempotencyKeyHash||n.command.context?.claimDeadline!==n.claimDeadlineAt||n.command.context?.executionDeadline!==n.executionDeadlineAt)return{ok:!1,reason:"request correlation mismatch"};try{const e={callerUserId:n.callerUserId,instanceId:n.instanceId,method:n.method,params:n.request.params};if(digest(RUNTIME_HASH_DOMAINS.request,n.request)!==n.requestHash||digest(RUNTIME_HASH_DOMAINS.params,{method:n.method,params:n.request.params})!==n.paramsHash||digest(RUNTIME_HASH_DOMAINS.command,{command:n.command})!==n.commandHash||digest(RUNTIME_HASH_DOMAINS.callerIntent,e)!==n.callerIntentHash||digest(RUNTIME_HASH_DOMAINS.idempotencyFingerprint,runtimeIdempotencyFingerprintPayload(n.callerUserId,n.instanceId,n.method,n.callerIntentHash))!==n.idempotencyFingerprint)return{ok:!1,reason:"canonical hash mismatch"}}catch{return{ok:!1,reason:"canonical hash mismatch"}}if(![n.claimDeadlineAt,n.executionDeadlineAt,n.createdAt].every(e=>"string"==typeof e&&Number.isFinite(Date.parse(e))))return{ok:!1,reason:"invalid request timestamp"};if(r){let e;try{e=validateRuntimeRpcRequestResourceBindings({type:"runtime_rpc_request",operationId:n.operationId,requestMessageId:n.requestMessageId,payload:n},Math.floor(Date.now()/1e3))}catch{return{ok:!1,reason:"resource binding validation failed"}}if(e.length)return{ok:!1,reason:"resource binding validation failed"}}return{ok:!0,envelope:t}}export function validateRuntimeCancelEnvelope(e){if(!record(e)||e.type!==RUNTIME_MESSAGE_TYPES.cancel||!record(e.payload))return{ok:!1,reason:"delivery is not a runtime cancel"};const t=e,n=t.payload;return exactKeys(n,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","eventId","requestedBy",...void 0===n.reason?[]:["reason"]])&&exactKeys(t,["message_id","offset","channel_id","type","payload","publisher_id","idempotency_key","created_at","state"])&&"completed"===t.state&&nonempty(t.message_id)&&Number.isInteger(t.offset)&&!(t.offset<1)&&nonempty(t.publisher_id)&&nonempty(t.idempotency_key)&&1===n.schemaVersion&&methods.has(n.method)&&decimal.test(n.dispatchEpoch)&&sha.test(n.requestHash)&&t.channel_id===n.operationId&&void 0===t.in_reply_to&&nonempty(n.eventId)&&nonempty(n.requestedBy)&&(void 0===n.reason||"string"==typeof n.reason)?{ok:!0,envelope:t}:{ok:!1,reason:"invalid cancel correlation"}}export function runtimeMessageBase(e){return{schemaVersion:1,operationId:e.operationId,requestMessageId:e.requestMessageId,requestHash:e.requestHash,dispatchEpoch:e.dispatchEpoch,method:e.method}}export function runtimeMessageHash(e,t){return digest(e,t)}function digest(e,t){return createHash("sha256").update(runtimeHashPreimage(e,t)).digest("hex")}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function nonempty(e){return"string"==typeof e&&e.length>0}function exactKeys(e,t){const n=Object.keys(e).sort(),r=[...t].sort();return n.length===r.length&&n.every((e,t)=>e===r[t])}export function semanticEqual(e,t){return canonicalizeJcs(e)===canonicalizeJcs(t)}
@@ -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{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}}),...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:t,command:n,executionGrant:a,getLease:i,journalRecord:s,abortSignal:o}){const c="skills.install"===n.name||"agent.applyTemplate"===n.name?n.params.artifact:void 0,u="mcp.set"===n.name?n.params.definition:void 0;if((c||u)&&(t.resourceBindingContractRevision!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||!t.resourceBindings))throw new RuntimeResourceResolutionError("resolution_rejected","resource-bound Service delivery requires the Types 4.1 binding carrier");const d=new HttpRuntimeOperationResourceResolver({gatewayUrl:e.agentGatewayUrl,operationId:t.operationId,requestMessageId:t.requestMessageId,instanceId:t.instanceId,paramsHash:t.paramsHash,bindings:t.resourceBindings??[],getAuthorization:()=>{const e=i();return{executionGrant:a,runtimeLeaseCredential:e.runtimeLeaseCredential,fence:deliveryFence(t,e)}},fetchAuthorized:async(t,r)=>await e.boundAgentFetch(t,r??{}),parseSkillArtifact:e=>parseAndValidateTemplatePackage(Buffer.from(e)),signal:o});if("prepared"!==s.phase&&"started"!==s.phase||(c&&await d.readVerifiedArtifact(c),u&&await d.resolveMcpDefinition(u)),"session.clear"!==n.name)return{resources:d};const m=n.context.conversation,p=i();return{resources:d,historyBoundary:new MessageServiceConversationHistoryBoundary({operationId:t.operationId,requestMessageId:t.requestMessageId,expectedGeneration:await r.currentGeneration(m.runtimeConversationUid),fence:deliveryFence(t,p),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){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:t.handlerIdentity,journalGeneration:t.runtimeEpoch}}
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{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(),o=e.attemptPorts??runtimeMessageAttemptPorts(r,n.serviceUrl);return{status:"ready",blockers:[],capabilityBlockers:CAPABILITY_BLOCKERS,serviceMethods:i,factory:new MsRuntimeMessageChannelConsumerFactory({channel:a,verifier:t,ports:o,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:t,command:n,executionGrant:a,getLease:i,journalRecord:o,abortSignal:s}){const c="skills.install"===n.name||"agent.applyTemplate"===n.name?n.params.artifact:void 0,u="mcp.set"===n.name?n.params.definition:void 0;if((c||u)&&(t.resourceBindingContractRevision!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||!t.resourceBindings))throw new RuntimeResourceResolutionError("resolution_rejected","resource-bound Service delivery requires the Types 4.1 binding carrier");const d=new HttpRuntimeOperationResourceResolver({gatewayUrl:e.agentGatewayUrl,operationId:t.operationId,requestMessageId:t.requestMessageId,instanceId:t.instanceId,paramsHash:t.paramsHash,bindings:t.resourceBindings??[],getAuthorization:()=>{const e=i();return{executionGrant:a,runtimeLeaseCredential:e.runtimeLeaseCredential,fence:deliveryFence(t,e)}},fetchAuthorized:async(t,r)=>await e.boundAgentFetch(t,r??{}),parseSkillArtifact:e=>parseAndValidateTemplatePackage(Buffer.from(e)),signal:s});if("prepared"!==o.phase&&"started"!==o.phase||(c&&await d.readVerifiedArtifact(c),u&&await d.resolveMcpDefinition(u)),"session.clear"!==n.name)return{resources:d};const m=n.context.conversation,l=i();return{resources:d,historyBoundary:new MessageServiceConversationHistoryBoundary({operationId:t.operationId,requestMessageId:t.requestMessageId,expectedGeneration:await r.currentGeneration(m.runtimeConversationUid),fence:deliveryFence(t,l),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){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:t.handlerIdentity,journalGeneration:t.runtimeEpoch}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.5.65-staging.1",
3
+ "version": "0.5.65-staging.3",
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>",
@@ -42,7 +42,7 @@
42
42
  "verify:release-evidence": "node ./scripts/verify-release-evidence.mjs",
43
43
  "verify:release-types-pin": "node ./scripts/verify-release-types-pin.mjs",
44
44
  "verify:release-package": "npm run build:release && node ./scripts/verify-release-package.mjs",
45
- "test:release-workflows": "node ./scripts/test-release-workflows.mjs && node --test ./scripts/openclaw-release-authority.test.mjs ./scripts/openclaw-release-dispatch.test.mjs ./scripts/openclaw-release-recovery.test.mjs ./scripts/verify-openclaw-dispatch-contract.test.mjs ./scripts/verify-promotion-evidence.test.mjs ./scripts/verify-package-equivalence.test.mjs ./scripts/verify-package-equivalence.e2e.test.mjs",
45
+ "test:release-workflows": "node ./scripts/test-release-workflows.mjs && node --test ./scripts/openclaw-release-authority.test.mjs ./scripts/openclaw-release-dispatch.test.mjs ./scripts/openclaw-release-finalizer.test.mjs ./scripts/openclaw-release-recovery.test.mjs ./scripts/verify-openclaw-dispatch-contract.test.mjs ./scripts/verify-promotion-evidence.test.mjs ./scripts/verify-package-equivalence.test.mjs ./scripts/verify-package-equivalence.e2e.test.mjs",
46
46
  "check:runtime-blackbox-worker": "node --check ./scripts/runtime-blackbox-worker.mjs && node --check ./scripts/runtime-blackbox-worker-main.mjs && node --check ./scripts/runtime-blackbox-worker-lifecycle.mjs && node --test ./scripts/runtime-blackbox-worker-lifecycle.test.mjs ./scripts/runtime-blackbox-worker-launcher.test.mjs",
47
47
  "prepublishOnly": "npm run verify:release-types-pin",
48
48
  "prepack": "npm run check:runtime-blackbox-worker && npm run build:release && node ./scripts/mcp-state-blackbox.mjs && node ./scripts/verify-release-package.mjs",
@@ -7,20 +7,47 @@ export async function withinDeadline(run, deadline) {
7
7
 
8
8
  const controller = new AbortController();
9
9
  let timer;
10
+ let timeoutError;
10
11
  const timeout = new Promise((_, reject) => {
11
12
  timer = setTimeout(() => {
12
- const error = workerTimeoutError();
13
- controller.abort(error);
14
- reject(error);
13
+ timeoutError = workerTimeoutError();
14
+ // Settle the deadline authority before aborting I/O. Some fetch/WebSocket
15
+ // implementations synchronously reject with their own AbortError, which
16
+ // must not win the race and erase the stable timeout classification.
17
+ reject(timeoutError);
18
+ controller.abort(timeoutError);
15
19
  }, remaining);
16
20
  });
17
21
  try {
18
22
  return await Promise.race([Promise.resolve().then(() => run(controller.signal)), timeout]);
23
+ } catch (error) {
24
+ if (timeoutError) throw timeoutError;
25
+ throw error;
19
26
  } finally {
20
27
  clearTimeout(timer);
21
28
  }
22
29
  }
23
30
 
31
+ /**
32
+ * Start a Gateway exactly once within the worker's existing deadline.
33
+ *
34
+ * GatewayClient deliberately rejects its first `start()` call when OpenClaw
35
+ * answers the handshake with the typed startup-only UNAVAILABLE response. It
36
+ * concurrently schedules its own reconnect. The runtime worker must keep
37
+ * that client alive long enough to observe the resulting `connected` event;
38
+ * invoking start() again would create competing reconnect loops.
39
+ */
40
+ export async function startGatewayWithinDeadline(gateway, deadline) {
41
+ try {
42
+ await withinDeadline(() => gateway.start(), deadline);
43
+ return;
44
+ } catch (error) {
45
+ if (!isRetryableGatewayHandshake(error)) throw error;
46
+ }
47
+
48
+ await withinDeadline((signal) => waitForGatewayConnection(gateway, signal), deadline);
49
+ }
50
+
24
51
  export async function closeRuntimeResources({ consumer, gateway }, cleanupTimeoutMs = 5_000) {
25
52
  let cleanupError;
26
53
  try {
@@ -45,6 +72,89 @@ export function selectRuntimeError(primaryError, cleanupError) {
45
72
  return primaryError ?? cleanupError;
46
73
  }
47
74
 
75
+ export function createRuntimeBlackboxCompletionGate() {
76
+ let terminal;
77
+ let acknowledged;
78
+ let failure;
79
+ let resolveCompleted;
80
+ let resolveFailed;
81
+ const completed = new Promise((resolve) => { resolveCompleted = resolve; });
82
+ const failed = new Promise((resolve) => { resolveFailed = resolve; });
83
+ const fail = () => {
84
+ if (failure) return;
85
+ failure = new Error("runtime message consumer delivery failed");
86
+ resolveFailed(failure);
87
+ };
88
+ return {
89
+ onTerminalAccepted(input) {
90
+ if (terminal || !input || typeof input.deliveryId !== "string" || !input.deliveryId || !input.message) {
91
+ fail(); return;
92
+ }
93
+ terminal = input;
94
+ },
95
+ onAcknowledged(input) {
96
+ if (!terminal || acknowledged || !input || input.deliveryId !== terminal.deliveryId ||
97
+ input.message !== terminal.message) {
98
+ fail(); return;
99
+ }
100
+ acknowledged = input;
101
+ resolveCompleted({ terminalEvent: terminal.message, deliveryId: terminal.deliveryId });
102
+ },
103
+ onDeliveryError() { fail(); },
104
+ async wait(deadline) {
105
+ return await withinDeadline(() => Promise.race([
106
+ completed,
107
+ failed.then((error) => { throw error; }),
108
+ ]), deadline);
109
+ },
110
+ assertNoFailure() { if (failure) throw failure; },
111
+ };
112
+ }
113
+
114
+ function isRetryableGatewayHandshake(error) {
115
+ return typeof error === "object" && error !== null &&
116
+ ((error.retryable === true) || error.code === "UNAVAILABLE");
117
+ }
118
+
119
+ function waitForGatewayConnection(gateway, signal) {
120
+ return new Promise((resolve, reject) => {
121
+ let settled = false;
122
+ const cleanup = () => {
123
+ gateway.off("connected", onConnected);
124
+ gateway.off("auth-failed", onAuthFailed);
125
+ gateway.off("error", onError);
126
+ signal.removeEventListener("abort", onAbort);
127
+ };
128
+ const settle = (callback, value) => {
129
+ if (settled) return;
130
+ settled = true;
131
+ cleanup();
132
+ callback(value);
133
+ };
134
+ const onConnected = () => settle(resolve);
135
+ const onAuthFailed = (failure) => {
136
+ const reason = failure && typeof failure.reason === "string"
137
+ ? failure.reason
138
+ : "Gateway authentication failed";
139
+ settle(reject, new Error(reason));
140
+ };
141
+ // GatewayClient emits `error` for each failed reconnect before scheduling
142
+ // the next one. Keep an error listener while waiting so EventEmitter does
143
+ // not crash the worker; auth-failed remains the immediate terminal signal.
144
+ const onError = () => {};
145
+ const onAbort = () => settle(reject, signal.reason ?? new Error("Gateway connection aborted"));
146
+
147
+ if (signal.aborted) {
148
+ onAbort();
149
+ return;
150
+ }
151
+ gateway.on("connected", onConnected);
152
+ gateway.on("auth-failed", onAuthFailed);
153
+ gateway.on("error", onError);
154
+ signal.addEventListener("abort", onAbort, { once: true });
155
+ });
156
+ }
157
+
48
158
  function workerTimeoutError() {
49
159
  return new Error(WORKER_TIMEOUT_MESSAGE);
50
160
  }
@@ -12,12 +12,14 @@ import { FileRuntimePoisonDeliveryJournal } from
12
12
  "../dist/runtime/runtime-poison-delivery-journal.js";
13
13
  import { MessageServiceRuntimeAuthenticatedFetch } from
14
14
  "../dist/runtime/message-service-runtime-authenticated-fetch.js";
15
+ import { MessageServiceRuntimeMessageChannel } from
16
+ "../dist/runtime/message-service-runtime-message-channel.js";
15
17
  import { AgentGatewayGrantJwksReadinessAdapter, RuntimeServiceTransportReadinessAdapter,
16
18
  createRuntimeServiceProductionCandidate } from
17
19
  "../dist/runtime/runtime-service-production-candidate.js";
18
20
  import { createRuntimeBlackboxReceipt } from "./runtime-security.mjs";
19
21
  import { runRuntimePoisonBlackbox } from "./runtime-poison-blackbox-lib.mjs";
20
- import { closeRuntimeResources, selectRuntimeError, withinDeadline } from
22
+ import { closeRuntimeResources, createRuntimeBlackboxCompletionGate, selectRuntimeError, startGatewayWithinDeadline, withinDeadline } from
21
23
  "./runtime-blackbox-worker-lifecycle.mjs";
22
24
 
23
25
  export async function main() {
@@ -69,21 +71,9 @@ export async function main() {
69
71
 
70
72
  let httpRequests = 0;
71
73
  let terminalEvent;
72
- let resolveTerminal;
73
- let rejectTerminal;
74
- const terminal = new Promise((resolve, reject) => { resolveTerminal = resolve; rejectTerminal = reject; });
75
74
  const countedFetch = async (url, init = {}) => {
76
75
  httpRequests += 1;
77
- const response = await fetchWithDeadline(url, init, deadline);
78
- if (String(init.method ?? "GET").toUpperCase() === "POST" &&
79
- /\/api\/v1\/runtime\/operations\/[^/]+\/messages$/.test(new URL(url).pathname) &&
80
- typeof init.body === "string" && JSON.parse(init.body).type === "runtime_rpc_response") {
81
- if (response.ok) {
82
- const result = await response.clone().json();
83
- terminalEvent = result.message; resolveTerminal(result.message);
84
- } else rejectTerminal(new Error(`terminal message rejected (${response.status})`));
85
- }
86
- return response;
76
+ return await fetchWithDeadline(url, init, deadline);
87
77
  };
88
78
  const messageFetch = new MessageServiceRuntimeAuthenticatedFetch({
89
79
  configuredServiceUrl: messageServiceUrl,
@@ -101,6 +91,7 @@ export async function main() {
101
91
  };
102
92
  const statePrefix = join(receiptDir, "claw-worker");
103
93
  const commandJournal = new FileRuntimeCommandJournal(`${statePrefix}-commands.json`);
94
+ const completionGate = createRuntimeBlackboxCompletionGate();
104
95
  const candidate = await withinDeadline(() => createRuntimeServiceProductionCandidate({
105
96
  authorizationAdapter: new AgentGatewayGrantJwksReadinessAdapter({ agentGatewayUrl }),
106
97
  transportAdapter: new RuntimeServiceTransportReadinessAdapter({ agentGatewayUrl,
@@ -108,17 +99,28 @@ export async function main() {
108
99
  scopedDeliveryKey: requiredEnvironment("MESSAGE_RUNTIME_DELIVERY_KEY") }),
109
100
  commandJournal,
110
101
  poisonJournal: new FileRuntimePoisonDeliveryJournal(`${statePrefix}-poison.json`),
102
+ onConsumerDeliveryError: completionGate.onDeliveryError,
103
+ onConsumerRequestTerminalAccepted: completionGate.onTerminalAccepted,
104
+ onConsumerRequestAcknowledged: completionGate.onAcknowledged,
111
105
  }), deadline);
112
106
  if (candidate.status !== "ready" || !candidate.serviceMethods.includes(contract.method?.acp)) {
113
107
  throw new Error(`production Service composition unavailable: ${candidate.blockers?.map((b) => b.id).join(",")}`);
114
108
  }
115
109
 
116
110
  const gateway = new GatewayClient({ url: openclawUrl, token: openclawToken,
117
- clientId: `runtime-blackbox-${process.pid}`, retry: { baseMs: 100, maxMs: 500, maxAttempts: 1 } });
111
+ clientId: `runtime-blackbox-${process.pid}`,
112
+ // GatewayClient owns reconnect scheduling. Sixty attempts at this capped
113
+ // 500ms backoff remain bounded by the worker's existing 30s deadline.
114
+ retry: { baseMs: 100, maxMs: 500, maxAttempts: 60 } });
118
115
  let consumer;
119
116
  let primaryError;
117
+ const runtimeLease = () => ({ instanceId, leaseId: nonempty(authority.leaseId, "authority.leaseId"),
118
+ handlerIdentity: nonempty(authority.handlerIdentity, "authority.handlerIdentity"),
119
+ runtimeEpoch: decimal(authority.runtimeEpoch, "authority.runtimeEpoch"),
120
+ runtimeLeaseCredential: nonempty(authority.runtimeLeaseCredential, "authority.runtimeLeaseCredential"),
121
+ leaseExpiresAt: new Date(Number(leaseClaims.exp) * 1_000).toISOString() });
120
122
  try {
121
- await withinDeadline(() => gateway.start(), deadline);
123
+ await startGatewayWithinDeadline(gateway, deadline);
122
124
  const identities = new FileRuntimeAgentIdentityRegistry(`${statePrefix}-identities.json`);
123
125
  const dispatcher = new OpenClawRuntimeCommandDispatcher({ getGateway: () => gateway,
124
126
  resources: rejectingResources(), identities,
@@ -131,18 +133,26 @@ export async function main() {
131
133
  consumer = await candidate.factory.create({ dispatcher,
132
134
  binding: { handlerIdentity: nonempty(authority.handlerIdentity, "authority.handlerIdentity"),
133
135
  journalStoreId, journalGeneration },
134
- getLease: () => ({ instanceId, leaseId: nonempty(authority.leaseId, "authority.leaseId"),
135
- handlerIdentity: nonempty(authority.handlerIdentity, "authority.handlerIdentity"),
136
- runtimeEpoch: decimal(authority.runtimeEpoch, "authority.runtimeEpoch"),
137
- runtimeLeaseCredential: nonempty(authority.runtimeLeaseCredential, "authority.runtimeLeaseCredential"),
138
- leaseExpiresAt: new Date(Number(leaseClaims.exp) * 1_000).toISOString() }) });
136
+ getLease: runtimeLease });
139
137
  consumer.start();
140
- await withinDeadline(() => terminal, deadline);
138
+ const completed = await completionGate.wait(deadline);
139
+ terminalEvent = completed.terminalEvent;
140
+ const authorityChannel = new MessageServiceRuntimeMessageChannel({ serviceUrl: messageServiceUrl,
141
+ transport: messageFetch, scopedDeliveryKey: requiredEnvironment("MESSAGE_RUNTIME_DELIVERY_KEY") });
142
+ const proof = await withinDeadline(() => authorityChannel.renew({
143
+ deliveryIds: [completed.deliveryId], lease: runtimeLease(),
144
+ }), deadline);
145
+ if (proof.renewed.length !== 0 || proof.notPending.length !== 1 ||
146
+ proof.notPending[0] !== completed.deliveryId) {
147
+ throw new Error("runtime delivery acknowledgement was not authoritative");
148
+ }
141
149
  } catch (error) {
142
150
  primaryError = error;
143
151
  } finally {
144
152
  primaryError = selectRuntimeError(primaryError,
145
153
  await closeRuntimeResources({ consumer, gateway }));
154
+ try { completionGate.assertNoFailure(); }
155
+ catch (error) { primaryError ??= error; }
146
156
  }
147
157
  if (primaryError) throw primaryError;
148
158
  const operationId = requiredEnvironment("RUNTIME_BLACKBOX_OPERATION_ID");
@@ -6,6 +6,7 @@ const CLOSED_FAILURES = new Set([
6
6
  "runtime blackbox worker timeout",
7
7
  "runtime lease/authority fixture binding mismatch",
8
8
  "production adapter emitted an invalid models/list terminal event",
9
+ "runtime message consumer delivery failed",
9
10
  "runtime blackbox worker cleanup failed",
10
11
  "RUNTIME_BLACKBOX_CONTRACT_FILE is required",
11
12
  ]);