beeos-claw 0.5.65-staging.4 → 0.5.65-staging.6
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{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
|
+
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;const e=new AbortController;this.controller=e;const t=this.loop(e.signal).catch(t=>{e.signal.aborted||this.observeDeliveryError(t)}).finally(()=>{this.running===t&&(this.running=void 0)});this.running=t}async stop(e){this.controller?.abort();for(const e of this.workers.values())e.cancel?.();this.running&&await abortable(this.running,e)}async readOperationHistory(e){const t=this.getLease();if(!t)return{status:"unavailable"};try{return await this.channel.history({operationId:e.operationId,lease:t})}catch{return{status:"unavailable"}}}async loop(e){let t;(()=>{t||(t=setInterval(()=>{this.renewActive().catch(()=>{})},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})})}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beeos-claw",
|
|
3
|
-
"version": "0.5.65-staging.
|
|
3
|
+
"version": "0.5.65-staging.6",
|
|
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>",
|
|
@@ -38,10 +38,10 @@ export async function withinDeadline(run, deadline) {
|
|
|
38
38
|
* invoking start() again would create competing reconnect loops.
|
|
39
39
|
*/
|
|
40
40
|
export async function startGatewayWithinDeadline(gateway, deadline) {
|
|
41
|
-
await withinDeadline((signal) => startAndObserveGateway(gateway, signal), deadline);
|
|
41
|
+
return await withinDeadline((signal) => startAndObserveGateway(gateway, signal), deadline);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export async function closeRuntimeResources({ consumer, gateway }, cleanupTimeoutMs = 5_000) {
|
|
44
|
+
export async function closeRuntimeResources({ consumer, gateway, releaseGatewayTransportObserver }, cleanupTimeoutMs = 5_000) {
|
|
45
45
|
let cleanupError;
|
|
46
46
|
try {
|
|
47
47
|
if (consumer) {
|
|
@@ -56,6 +56,11 @@ export async function closeRuntimeResources({ consumer, gateway }, cleanupTimeou
|
|
|
56
56
|
}
|
|
57
57
|
} catch (error) {
|
|
58
58
|
cleanupError ??= new Error(WORKER_CLEANUP_MESSAGE, { cause: error });
|
|
59
|
+
} finally {
|
|
60
|
+
// The retained observer is only needed while GatewayClient can still
|
|
61
|
+
// emit a transport error. Release it after the bounded stop completes,
|
|
62
|
+
// including the consumer-stop failure path.
|
|
63
|
+
releaseGatewayTransportObserver?.();
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
return cleanupError;
|
|
@@ -113,20 +118,25 @@ function startAndObserveGateway(gateway, signal) {
|
|
|
113
118
|
return new Promise((resolve, reject) => {
|
|
114
119
|
let settled = false;
|
|
115
120
|
let observedTransportFailure = false;
|
|
116
|
-
const cleanup = () => {
|
|
121
|
+
const cleanup = (retainTransportObserver = false) => {
|
|
117
122
|
gateway.off("connected", onConnected);
|
|
118
123
|
gateway.off("auth-failed", onAuthFailed);
|
|
119
|
-
gateway.off("error", onError);
|
|
124
|
+
if (!retainTransportObserver) gateway.off("error", onError);
|
|
120
125
|
gateway.off("disconnected", onDisconnected);
|
|
121
126
|
signal.removeEventListener("abort", onAbort);
|
|
122
127
|
};
|
|
123
|
-
const
|
|
128
|
+
const releaseTransportObserver = () => gateway.off("error", onError);
|
|
129
|
+
const settle = (callback, value, retainTransportObserver = false) => {
|
|
124
130
|
if (settled) return;
|
|
125
131
|
settled = true;
|
|
126
|
-
cleanup();
|
|
132
|
+
cleanup(retainTransportObserver);
|
|
127
133
|
callback(value);
|
|
128
134
|
};
|
|
129
|
-
|
|
135
|
+
// `error` has special EventEmitter semantics: after the initial handshake
|
|
136
|
+
// succeeds, a later WebSocket transport error must stay observed until the
|
|
137
|
+
// worker shuts the Gateway down. GatewayClient owns reconnects; this
|
|
138
|
+
// observer only prevents that owned transient from becoming process-fatal.
|
|
139
|
+
const onConnected = () => settle(resolve, releaseTransportObserver, true);
|
|
130
140
|
const onAuthFailed = (failure) => {
|
|
131
141
|
const reason = failure && typeof failure.reason === "string"
|
|
132
142
|
? failure.reason
|
|
@@ -154,7 +164,7 @@ function startAndObserveGateway(gateway, signal) {
|
|
|
154
164
|
Promise.resolve()
|
|
155
165
|
.then(() => gateway.start())
|
|
156
166
|
.then(
|
|
157
|
-
() => settle(resolve),
|
|
167
|
+
() => settle(resolve, releaseTransportObserver, true),
|
|
158
168
|
(error) => {
|
|
159
169
|
// Typed handshake UNAVAILABLE and a pre-auth transport failure both
|
|
160
170
|
// leave GatewayClient's single bounded reconnect loop authoritative.
|
|
@@ -114,13 +114,14 @@ export async function main() {
|
|
|
114
114
|
retry: { baseMs: 100, maxMs: 500, maxAttempts: 60 } });
|
|
115
115
|
let consumer;
|
|
116
116
|
let primaryError;
|
|
117
|
+
let releaseGatewayTransportObserver;
|
|
117
118
|
const runtimeLease = () => ({ instanceId, leaseId: nonempty(authority.leaseId, "authority.leaseId"),
|
|
118
119
|
handlerIdentity: nonempty(authority.handlerIdentity, "authority.handlerIdentity"),
|
|
119
120
|
runtimeEpoch: decimal(authority.runtimeEpoch, "authority.runtimeEpoch"),
|
|
120
121
|
runtimeLeaseCredential: nonempty(authority.runtimeLeaseCredential, "authority.runtimeLeaseCredential"),
|
|
121
122
|
leaseExpiresAt: new Date(Number(leaseClaims.exp) * 1_000).toISOString() });
|
|
122
123
|
try {
|
|
123
|
-
await startGatewayWithinDeadline(gateway, deadline);
|
|
124
|
+
releaseGatewayTransportObserver = await startGatewayWithinDeadline(gateway, deadline);
|
|
124
125
|
const identities = new FileRuntimeAgentIdentityRegistry(`${statePrefix}-identities.json`);
|
|
125
126
|
const dispatcher = new OpenClawRuntimeCommandDispatcher({ getGateway: () => gateway,
|
|
126
127
|
resources: rejectingResources(), identities,
|
|
@@ -150,7 +151,7 @@ export async function main() {
|
|
|
150
151
|
primaryError = error;
|
|
151
152
|
} finally {
|
|
152
153
|
primaryError = selectRuntimeError(primaryError,
|
|
153
|
-
await closeRuntimeResources({ consumer, gateway }));
|
|
154
|
+
await closeRuntimeResources({ consumer, gateway, releaseGatewayTransportObserver }));
|
|
154
155
|
try { completionGate.assertNoFailure(); }
|
|
155
156
|
catch (error) { primaryError ??= error; }
|
|
156
157
|
}
|