beeos-claw 0.6.63-cloud.13 → 0.6.63-cloud.15

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{isCloudRuntimeSubject,RUNTIME_HASH_DOMAINS,runtimeCloudCallerIntentPayload,runtimeCloudIdempotencyFingerprintPayload}from"@beeos-ai/beeos-types/runtime";import{runtimeMessageHash,semanticEqual}from"./runtime-message-channel-contract.js";import{RuntimeCommandError}from"./runtime-command-dispatcher.js";import{isDeepStrictEqual}from"node:util";const verifiedPrompts=new WeakMap;export function retainedVerifiedNativePrompt(e){if(!e||"object"!=typeof e)return null;const t=verifiedPrompts.get(e);return t&&isDeepStrictEqual(e,t)?structuredClone(t):null}const object=e=>!!e&&"object"==typeof e&&!Array.isArray(e),nonempty=e=>"string"==typeof e&&!!e,digest=e=>"string"==typeof e&&/^[0-9a-f]{64}$/.test(e),decimal=e=>"string"==typeof e&&/^(0|[1-9][0-9]*)$/.test(e),exact=(e,t)=>Object.keys(e).sort().join("\0")===t.sort().join("\0"),fail=()=>{throw new RuntimeCommandError("INVALID_PARAMS","native prompt signed binding invalid")};export function verifyNativePromptAuthority(e){const t=e.request,a=e.decoded?.claims;if(!(object(t)&&object(a)&&object(t.request)&&object(t.command)&&object(t.command.context)&&object(t.command.params)&&object(t.request.params)))return fail();const n=t.request.params,r=t.command.context,o=n.runtimeLease;if(!(exact(t,["schemaVersion","authorityRealm","cloudSubject","operationId","requestMessageId","requestHash","paramsHash","commandHash","idempotencyKeyHash","idempotencyFingerprint","callerIntentHash","instanceId","leaseId","runtimeHandlerIdentity","dispatchEpoch","executionGrantHash","claimDeadlineAt","executionDeadlineAt","createdAt","method","request","command","callerExternalUserId"])&&exact(t.request,["jsonrpc","id","method","params"])&&exact(t.command,["context","name","params"])&&exact(n,["runtimeRunId","parts","conversationId","platformAgentId","taskId","taskRequestMessageId","replyMessageId","inputFingerprint","runtimeLease",...["mentions","mode","modelOverrideId"].filter(e=>void 0!==n[e])])&&object(o)&&exact(o,["runtimeEpoch","leaseId","journalStoreId","journalGeneration"])&&2===t.schemaVersion&&"cloud"===t.authorityRealm&&"session/prompt"===t.method&&t.request.method===t.method&&"2.0"===t.request.jsonrpc&&("string"==typeof t.request.id||"number"==typeof t.request.id)&&"session.prompt"===t.command.name&&isCloudRuntimeSubject(t.cloudSubject)&&isCloudRuntimeSubject(a.cloudSubject)&&semanticEqual(t.cloudSubject,a.cloudSubject)&&t.cloudSubject.instanceId===e.lease.instanceId&&t.instanceId===e.lease.instanceId&&t.runtimeHandlerIdentity===e.lease.handlerIdentity&&t.leaseId===e.lease.leaseId&&decimal(t.dispatchEpoch)&&decimal(o.runtimeEpoch)&&decimal(o.journalGeneration)&&o.runtimeEpoch===e.lease.runtimeEpoch&&o.leaseId===e.lease.leaseId&&o.journalStoreId===e.lineage.journalStoreId&&o.journalGeneration===e.lineage.journalGeneration&&nonempty(t.callerExternalUserId)&&nonempty(t.requestMessageId)&&t.requestMessageId!==n.taskRequestMessageId&&n.replyMessageId!==n.taskRequestMessageId&&[n.conversationId,n.platformAgentId,n.taskRequestMessageId,n.replyMessageId].every(nonempty)&&digest(n.inputFingerprint)&&n.taskId===t.operationId&&n.runtimeRunId===t.operationId&&"string"==typeof n.taskId&&/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(n.taskId)&&Array.isArray(n.parts)&&(void 0===n.mode||"string"==typeof n.mode)&&(void 0===n.modelOverrideId||"string"==typeof n.modelOverrideId)&&(void 0===n.mentions||Array.isArray(n.mentions))&&"conversation"===r.scope&&r.operationId===t.operationId&&r.dispatchEpoch===t.dispatchEpoch&&r.runtimeInstanceUid===t.instanceId&&r.paramsHash===t.paramsHash&&r.idempotencyKeyHash===t.idempotencyKeyHash&&r.claimDeadline===t.claimDeadlineAt&&r.executionDeadline===t.executionDeadlineAt&&decimal(r.historyGeneration)&&object(r.conversation)&&r.conversation.runtimeConversationUid===n.conversationId&&r.conversation.runtimeSessionKey===n.conversationId&&[t.requestHash,t.paramsHash,t.commandHash,t.callerIntentHash,t.idempotencyFingerprint,t.idempotencyKeyHash,t.executionGrantHash].every(digest)))return fail();const s={runtimeRunId:n.runtimeRunId,parts:n.parts,...void 0===n.mentions?{}:{mentions:n.mentions},...void 0===n.mode?{}:{mode:n.mode},...void 0===n.modelOverrideId?{}:{modelOverrideId:n.modelOverrideId}},i=e.now??Date.now();if(![t.createdAt,t.claimDeadlineAt,t.executionDeadlineAt].every(e=>"string"==typeof e&&Number.isFinite(Date.parse(e)))||Date.parse(t.createdAt)>Date.parse(t.claimDeadlineAt)||Date.parse(t.claimDeadlineAt)>Date.parse(t.executionDeadlineAt)||!e.allowExpiredReadOnly&&Date.parse(t.claimDeadlineAt)<=i||!e.allowExpiredReadOnly&&Date.parse(t.executionDeadlineAt)<=i||!semanticEqual(s,t.command.params)||runtimeMessageHash(RUNTIME_HASH_DOMAINS.request,t.request)!==t.requestHash||runtimeMessageHash(RUNTIME_HASH_DOMAINS.params,{method:t.method,params:n})!==t.paramsHash||runtimeMessageHash(RUNTIME_HASH_DOMAINS.command,{command:t.command})!==t.commandHash||runtimeMessageHash(RUNTIME_HASH_DOMAINS.cloudCallerIntent,runtimeCloudCallerIntentPayload(t.cloudSubject,"session/prompt",n,t.callerExternalUserId))!==t.callerIntentHash||runtimeMessageHash(RUNTIME_HASH_DOMAINS.cloudIdempotencyFingerprint,runtimeCloudIdempotencyFingerprintPayload(t.cloudSubject,"session/prompt",t.callerIntentHash))!==t.idempotencyFingerprint||e.decoded?.compactHash!==t.executionGrantHash)return fail();const d=Math.floor(i/1e3);if(!exact(a,["iss","aud","purpose","sub","jti","iat","exp","authorityRealm","cloudSubject","operationId","requestMessageId","instanceId","leaseId","handlerIdentity","dispatchEpoch","requestHash","paramsHash","commandHash","idempotencyKeyHash","executionMode","method","command","scope","callerExternalUserId","platformAgentId","runtimeAgentUid","platformConversationId","historyGeneration"])||"beeos-cloud-gateway"!==a.iss||"beeos-cloud-runtime-command"!==a.aud||"cloud-runtime-execution-grant"!==a.purpose||"cloud"!==a.authorityRealm||"start"!==a.executionMode||"conversation"!==a.scope||!nonempty(a.jti)||!nonempty(a.runtimeAgentUid)||!Number.isSafeInteger(a.iat)||!Number.isSafeInteger(a.exp)||a.iat>d||!e.allowExpiredReadOnly&&a.exp<=d||a.exp<=a.iat||a.exp-a.iat>60||a.sub!==e.lease.handlerIdentity||a.handlerIdentity!==e.lease.handlerIdentity||a.platformAgentId!==n.platformAgentId||a.platformConversationId!==n.conversationId||a.historyGeneration!==r.historyGeneration||!semanticEqual(a.command,t.command)||!semanticEqual(a.cloudSubject,t.cloudSubject)||["operationId","requestMessageId","instanceId","leaseId","dispatchEpoch","requestHash","paramsHash","commandHash","idempotencyKeyHash","callerExternalUserId","method"].some(e=>a[e]!==t[e]))return fail();const m=structuredClone(t),c={historyGeneration:r.historyGeneration,runtimeAgentUid:a.runtimeAgentUid,request:m,params:m.request.params,command:m.command};return verifiedPrompts.set(c,structuredClone(c)),c}
1
+ import{isCloudRuntimeSubject,RUNTIME_HASH_DOMAINS,runtimeCloudCallerIntentPayload,runtimeCloudIdempotencyFingerprintPayload}from"@beeos-ai/beeos-types/runtime";import{runtimeMessageHash,semanticEqual}from"./runtime-message-channel-contract.js";import{RuntimeCommandError}from"./runtime-command-dispatcher.js";import{isDeepStrictEqual}from"node:util";const verifiedPrompts=new WeakMap;export function retainedVerifiedNativePrompt(e){if(!e||"object"!=typeof e)return null;const t=verifiedPrompts.get(e);return t&&isDeepStrictEqual(e,t)?structuredClone(t):null}const object=e=>!!e&&"object"==typeof e&&!Array.isArray(e),nonempty=e=>"string"==typeof e&&!!e,digest=e=>"string"==typeof e&&/^[0-9a-f]{64}$/.test(e),decimal=e=>"string"==typeof e&&/^(0|[1-9][0-9]*)$/.test(e),exact=(e,t)=>Object.keys(e).sort().join("\0")===t.sort().join("\0"),fail=()=>{throw new RuntimeCommandError("INVALID_PARAMS","native prompt signed binding invalid")},validReasoning=e=>object(e)&&exact(e,["modelId","reasoningOverrideId"])&&nonempty(e.modelId)&&(null===e.reasoningOverrideId||nonempty(e.reasoningOverrideId));export function verifyNativePromptAuthority(e){const t=e.request,a=e.decoded?.claims;if(!(object(t)&&object(a)&&object(t.request)&&object(t.command)&&object(t.command.context)&&object(t.command.params)&&object(t.request.params)))return fail();const n=t.request.params,r=t.command.context,o=n.runtimeLease;if(!(exact(t,["schemaVersion","authorityRealm","cloudSubject","operationId","requestMessageId","requestHash","paramsHash","commandHash","idempotencyKeyHash","idempotencyFingerprint","callerIntentHash","instanceId","leaseId","runtimeHandlerIdentity","dispatchEpoch","executionGrantHash","claimDeadlineAt","executionDeadlineAt","createdAt","method","request","command","callerExternalUserId"])&&exact(t.request,["jsonrpc","id","method","params"])&&exact(t.command,["context","name","params"])&&exact(n,["runtimeRunId","parts","conversationId","platformAgentId","taskId","taskRequestMessageId","replyMessageId","inputFingerprint","runtimeLease",...["mentions","mode","modelOverrideId","reasoning"].filter(e=>void 0!==n[e])])&&object(o)&&exact(o,["runtimeEpoch","leaseId","journalStoreId","journalGeneration"])&&2===t.schemaVersion&&"cloud"===t.authorityRealm&&"session/prompt"===t.method&&t.request.method===t.method&&"2.0"===t.request.jsonrpc&&("string"==typeof t.request.id||"number"==typeof t.request.id)&&"session.prompt"===t.command.name&&isCloudRuntimeSubject(t.cloudSubject)&&isCloudRuntimeSubject(a.cloudSubject)&&semanticEqual(t.cloudSubject,a.cloudSubject)&&t.cloudSubject.instanceId===e.lease.instanceId&&t.instanceId===e.lease.instanceId&&t.runtimeHandlerIdentity===e.lease.handlerIdentity&&t.leaseId===e.lease.leaseId&&decimal(t.dispatchEpoch)&&decimal(o.runtimeEpoch)&&decimal(o.journalGeneration)&&o.runtimeEpoch===e.lease.runtimeEpoch&&o.leaseId===e.lease.leaseId&&o.journalStoreId===e.lineage.journalStoreId&&o.journalGeneration===e.lineage.journalGeneration&&nonempty(t.callerExternalUserId)&&nonempty(t.requestMessageId)&&t.requestMessageId!==n.taskRequestMessageId&&n.replyMessageId!==n.taskRequestMessageId&&[n.conversationId,n.platformAgentId,n.taskRequestMessageId,n.replyMessageId].every(nonempty)&&digest(n.inputFingerprint)&&n.taskId===t.operationId&&n.runtimeRunId===t.operationId&&"string"==typeof n.taskId&&/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(n.taskId)&&Array.isArray(n.parts)&&(void 0===n.mode||"string"==typeof n.mode)&&(void 0===n.modelOverrideId||"string"==typeof n.modelOverrideId)&&(void 0===n.reasoning||validReasoning(n.reasoning))&&(void 0===n.mentions||Array.isArray(n.mentions))&&"conversation"===r.scope&&r.operationId===t.operationId&&r.dispatchEpoch===t.dispatchEpoch&&r.runtimeInstanceUid===t.instanceId&&r.paramsHash===t.paramsHash&&r.idempotencyKeyHash===t.idempotencyKeyHash&&r.claimDeadline===t.claimDeadlineAt&&r.executionDeadline===t.executionDeadlineAt&&decimal(r.historyGeneration)&&object(r.conversation)&&r.conversation.runtimeConversationUid===n.conversationId&&r.conversation.runtimeSessionKey===n.conversationId&&[t.requestHash,t.paramsHash,t.commandHash,t.callerIntentHash,t.idempotencyFingerprint,t.idempotencyKeyHash,t.executionGrantHash].every(digest)))return fail();const i={runtimeRunId:n.runtimeRunId,parts:n.parts,...void 0===n.mentions?{}:{mentions:n.mentions},...void 0===n.mode?{}:{mode:n.mode},...void 0===n.modelOverrideId?{}:{modelOverrideId:n.modelOverrideId},...void 0===n.reasoning?{}:{reasoning:n.reasoning}},s=e.now??Date.now();if(![t.createdAt,t.claimDeadlineAt,t.executionDeadlineAt].every(e=>"string"==typeof e&&Number.isFinite(Date.parse(e)))||Date.parse(t.createdAt)>Date.parse(t.claimDeadlineAt)||Date.parse(t.claimDeadlineAt)>Date.parse(t.executionDeadlineAt)||!e.allowExpiredReadOnly&&Date.parse(t.claimDeadlineAt)<=s||!e.allowExpiredReadOnly&&Date.parse(t.executionDeadlineAt)<=s||!semanticEqual(i,t.command.params)||runtimeMessageHash(RUNTIME_HASH_DOMAINS.request,t.request)!==t.requestHash||runtimeMessageHash(RUNTIME_HASH_DOMAINS.params,{method:t.method,params:n})!==t.paramsHash||runtimeMessageHash(RUNTIME_HASH_DOMAINS.command,{command:t.command})!==t.commandHash||runtimeMessageHash(RUNTIME_HASH_DOMAINS.cloudCallerIntent,runtimeCloudCallerIntentPayload(t.cloudSubject,"session/prompt",n,t.callerExternalUserId))!==t.callerIntentHash||runtimeMessageHash(RUNTIME_HASH_DOMAINS.cloudIdempotencyFingerprint,runtimeCloudIdempotencyFingerprintPayload(t.cloudSubject,"session/prompt",t.callerIntentHash))!==t.idempotencyFingerprint||e.decoded?.compactHash!==t.executionGrantHash)return fail();const d=Math.floor(s/1e3);if(!exact(a,["iss","aud","purpose","sub","jti","iat","exp","authorityRealm","cloudSubject","operationId","requestMessageId","instanceId","leaseId","handlerIdentity","dispatchEpoch","requestHash","paramsHash","commandHash","idempotencyKeyHash","executionMode","method","command","scope","callerExternalUserId","platformAgentId","runtimeAgentUid","platformConversationId","historyGeneration"])||"beeos-cloud-gateway"!==a.iss||"beeos-cloud-runtime-command"!==a.aud||"cloud-runtime-execution-grant"!==a.purpose||"cloud"!==a.authorityRealm||"start"!==a.executionMode||"conversation"!==a.scope||!nonempty(a.jti)||!nonempty(a.runtimeAgentUid)||!Number.isSafeInteger(a.iat)||!Number.isSafeInteger(a.exp)||a.iat>d||!e.allowExpiredReadOnly&&a.exp<=d||a.exp<=a.iat||a.exp-a.iat>60||a.sub!==e.lease.handlerIdentity||a.handlerIdentity!==e.lease.handlerIdentity||a.platformAgentId!==n.platformAgentId||a.platformConversationId!==n.conversationId||a.historyGeneration!==r.historyGeneration||!semanticEqual(a.command,t.command)||!semanticEqual(a.cloudSubject,t.cloudSubject)||["operationId","requestMessageId","instanceId","leaseId","dispatchEpoch","requestHash","paramsHash","commandHash","idempotencyKeyHash","callerExternalUserId","method"].some(e=>a[e]!==t[e]))return fail();const m=structuredClone(t),c={historyGeneration:r.historyGeneration,runtimeAgentUid:a.runtimeAgentUid,request:m,params:m.request.params,command:m.command};return verifiedPrompts.set(c,structuredClone(c)),c}
@@ -1 +1 @@
1
- export class OpenClawRuntimeCapabilities{deps;constructor(t){this.deps=t}async execute(t,e,s){const i=t;if(i.name.startsWith("session."))return await this.deps.sessions.execute(i,e,s);if(i.name.startsWith("skills."))return await this.deps.skills.execute(i,e,s);if(i.name.startsWith("mcp."))return await this.deps.mcp.execute(i,e,s);if(i.name.startsWith("terminal."))return await this.deps.terminal.execute(i,e,s);if(i.name.startsWith("canvas."))return await this.deps.canvas.execute(i,e,s);if(i.name.startsWith("cron."))return await this.deps.cron.execute(i,e,s);const n=this.requireGateway();switch(i.name){case"models.list":return normalizeModels(await n.request("models.list",{}));case"agent.listProjection":return await this.listAgents(n)}throw new Error(`canonical capability has no owner: ${i.name}`)}async cancel(t){for(const e of[this.deps.sessions,this.deps.skills,this.deps.mcp,this.deps.terminal,this.deps.canvas,this.deps.cron]){const s=await(e.cancel?.(t));if("cancel_requested"===s)return s}return"not_cancellable"}async afterCommit(t,e){const s=t.name.startsWith("session.")?this.deps.sessions:t.name.startsWith("skills.")?this.deps.skills:t.name.startsWith("mcp.")?this.deps.mcp:t.name.startsWith("terminal.")?this.deps.terminal:t.name.startsWith("canvas.")?this.deps.canvas:t.name.startsWith("cron.")?this.deps.cron:void 0;await(s?.afterCommit?.(t,e))}async projectCommitted(t,e,s){const i=t.name.startsWith("session.")?this.deps.sessions:t.name.startsWith("skills.")?this.deps.skills:t.name.startsWith("mcp.")?this.deps.mcp:t.name.startsWith("terminal.")?this.deps.terminal:t.name.startsWith("canvas.")?this.deps.canvas:t.name.startsWith("cron.")?this.deps.cron:void 0,n=i?.projectCommitted;return!!n&&(await n.call(i,t,e,s)??!0)}requireGateway(){const t=this.deps.getGateway();if(!t?.isConnected)throw new Error("Gateway not connected");return t}async listAgents(t){const e=await t.request("agents.list",{}),s=[];for(const t of e?.agents??[]){const e=t.id?.trim();e&&s.push({runtimeAgentUid:await this.deps.identities.resolveOrCreate(e),runtimeLocalKey:e,name:t.identity?.name||t.name||e,...t.identity?.emoji?{emoji:t.identity.emoji}:{},...t.identity?.avatarUrl||t.identity?.avatar?{avatarUrl:t.identity.avatarUrl||t.identity.avatar}:{}})}return{...e?.defaultId?{defaultRuntimeLocalKey:e.defaultId}:{},agents:s}}}function normalizeModels(t){return{models:(t?.models??[]).flatMap(t=>{if(!t||"object"!=typeof t)return[];const e=t,s="string"==typeof e.id?e.id:"";if(!s)return[];const i="string"==typeof e.provider?e.provider:s.split("/")[0]||"unknown",n=Array.isArray(e.input)?e.input.filter(t=>"text"===t||"image"===t||"document"===t):void 0,a=e.pricing&&"object"==typeof e.pricing?e.pricing:void 0;return[{id:s,name:"string"==typeof e.name?e.name:s,provider:i,..."number"==typeof e.contextWindow?{contextWindow:e.contextWindow}:{},..."boolean"==typeof e.reasoning?{reasoning:e.reasoning}:{},...n?.length?{input:n}:{},..."string"==typeof a?.prompt&&"string"==typeof a.completion?{pricing:{prompt:a.prompt,completion:a.completion}}:{},..."number"==typeof e.completionMultiplier?{completionMultiplier:e.completionMultiplier}:{}}]})}}
1
+ export class OpenClawRuntimeCapabilities{deps;constructor(t){this.deps=t}async execute(t,e,i){const s=t;if(s.name.startsWith("session."))return await this.deps.sessions.execute(s,e,i);if(s.name.startsWith("skills."))return await this.deps.skills.execute(s,e,i);if(s.name.startsWith("mcp."))return await this.deps.mcp.execute(s,e,i);if(s.name.startsWith("terminal."))return await this.deps.terminal.execute(s,e,i);if(s.name.startsWith("canvas."))return await this.deps.canvas.execute(s,e,i);if(s.name.startsWith("cron."))return await this.deps.cron.execute(s,e,i);const n=this.requireGateway();switch(s.name){case"models.list":return normalizeModels(await n.request("models.list",{}),await n.request("agents.list",{}));case"agent.listProjection":return await this.listAgents(n)}throw new Error(`canonical capability has no owner: ${s.name}`)}async cancel(t){for(const e of[this.deps.sessions,this.deps.skills,this.deps.mcp,this.deps.terminal,this.deps.canvas,this.deps.cron]){const i=await(e.cancel?.(t));if("cancel_requested"===i)return i}return"not_cancellable"}async afterCommit(t,e){const i=t.name.startsWith("session.")?this.deps.sessions:t.name.startsWith("skills.")?this.deps.skills:t.name.startsWith("mcp.")?this.deps.mcp:t.name.startsWith("terminal.")?this.deps.terminal:t.name.startsWith("canvas.")?this.deps.canvas:t.name.startsWith("cron.")?this.deps.cron:void 0;await(i?.afterCommit?.(t,e))}async projectCommitted(t,e,i){const s=t.name.startsWith("session.")?this.deps.sessions:t.name.startsWith("skills.")?this.deps.skills:t.name.startsWith("mcp.")?this.deps.mcp:t.name.startsWith("terminal.")?this.deps.terminal:t.name.startsWith("canvas.")?this.deps.canvas:t.name.startsWith("cron.")?this.deps.cron:void 0,n=s?.projectCommitted;return!!n&&(await n.call(s,t,e,i)??!0)}requireGateway(){const t=this.deps.getGateway();if(!t?.isConnected)throw new Error("Gateway not connected");return t}async listAgents(t){const e=await t.request("agents.list",{}),i=[];for(const t of e?.agents??[]){const e=t.id?.trim();e&&i.push({runtimeAgentUid:await this.deps.identities.resolveOrCreate(e),runtimeLocalKey:e,name:t.identity?.name||t.name||e,...t.identity?.emoji?{emoji:t.identity.emoji}:{},...t.identity?.avatarUrl||t.identity?.avatar?{avatarUrl:t.identity.avatarUrl||t.identity.avatar}:{}})}return{...e?.defaultId?{defaultRuntimeLocalKey:e.defaultId}:{},agents:i}}}export function reasoningOptionsByModel(t){const e=t,i=[...(e?.agents??[]).flatMap(t=>t&&"object"==typeof t?[t]:[])].sort((t,i)=>Number(t.id===e?.defaultId)-Number(i.id===e?.defaultId)),s=new Map;for(const t of i){const e=t.model,i=e&&"object"==typeof e?e.primary:void 0,n=thinkingProfile(t.thinkingLevels,t.thinkingDefault);"string"==typeof i&&i.trim()&&n&&s.set(catalogModelKey(i),n)}return s}function catalogModelKey(t){const e=t.trim();return e.startsWith("arouter/")?e.slice(8):e}function thinkingProfile(t,e){if(!Array.isArray(t))return;const i=t.flatMap(t=>{if(!t||"object"!=typeof t)return[];const e=t.id;if("string"!=typeof e||!e.trim())return[];const i=t.label;return[{id:e,..."string"==typeof i&&i?{label:i}:{}}]});if(!i.length)return;const s="string"==typeof e&&i.some(t=>t.id===e)?e:null;return{levels:i,defaultLevel:s}}function normalizeModels(t,e){const i=reasoningOptionsByModel(e);return{models:(t?.models??[]).flatMap(t=>{if(!t||"object"!=typeof t)return[];const e=t,s="string"==typeof e.id?e.id:"";if(!s)return[];const n="string"==typeof e.provider?e.provider:s.split("/")[0]||"unknown",a=Array.isArray(e.input)?e.input.filter(t=>"text"===t||"image"===t||"document"===t):void 0,r=e.pricing&&"object"==typeof e.pricing?e.pricing:void 0,o=i.get(catalogModelKey(s));return[{id:s,name:"string"==typeof e.name?e.name:s,provider:n,..."number"==typeof e.contextWindow?{contextWindow:e.contextWindow}:{},..."boolean"==typeof e.reasoning?{reasoning:e.reasoning}:{},...o?{reasoningOptions:o}:{},...a?.length?{input:a}:{},..."string"==typeof r?.prompt&&"string"==typeof r.completion?{pricing:{prompt:r.prompt,completion:r.completion}}:{},..."number"==typeof e.completionMultiplier?{completionMultiplier:e.completionMultiplier}:{}}]})}}
@@ -1 +1 @@
1
- import{createHash}from"node:crypto";import{hashRuntimeResourceRedemptionPayload,normalizeRuntimeResourceRedemptionHashPayload,validateRuntimeMcpResourceRedemptionCompletion,validateRuntimeSkillResourceRedemptionCompletion}from"@beeos-ai/beeos-types/runtime";export class RuntimeResourceResolutionError extends Error{kind;code;diagnostic;constructor(e,t,i,r){super(r?`${t} [${r}]`:t),this.kind=e,this.code=i,this.diagnostic=r,this.name="RuntimeResourceResolutionError"}}export class HttpRuntimeOperationResourceResolver{options;attempts=new Map;byRef;mcp;skill;constructor(e){this.options=e;const t=e.bindings.map(e=>[e.resourceRef,Object.freeze({...e})]);if(new Set(t.map(([e])=>e)).size!==t.length)throw new Error("duplicate resource binding");this.byRef=new Map(t)}async readContent(){throw new Error("Service inline content is forbidden; use a trusted artifact reference")}async resolveMcpDefinition(e){this.mcp||(this.mcp=this.redeemMcp(e).catch(e=>{throw this.mcp=void 0,e}));const t=await this.mcp;if(!sameMcpDescriptor(t.descriptor,e))throw new Error("MCP resource descriptor mismatch");return t.config}async resolveMcpCredential(e){this.mcp||(this.mcp=this.redeemMcp(e).catch(e=>{throw this.mcp=void 0,e}));const t=await this.mcp;if(!sameMcpDescriptor(t.descriptor,e))throw new Error("MCP resource descriptor mismatch");return t.credential}async readVerifiedArtifact(e){this.skill||(this.skill=this.redeemSkill(e).catch(e=>{throw this.skill=void 0,e}));const t=await this.skill;if(!sameArtifactDescriptor(t.descriptor,e))throw new Error("skill resource descriptor mismatch");return t.parsed}async redeemMcp(e){const t=this.bindingFor("mcp",e.definitionRef);if(t.version!==e.version||t.sha256!==e.sha256)throw resolution("MCP descriptor does not match the immutable authority binding","descriptor_binding_mismatch");const{request:i,reservation:r}=this.requestFor(t),s=await this.redeem(t,i);let o;try{o=await s.json()}catch{throw integrity("MCP authority response is invalid","response_not_json")}const n=validateRuntimeMcpResourceRedemptionCompletion({request:i,binding:t,reservation:r,expectedDisposition:o.disposition,result:o,sha256Utf8:sha256Utf8});if(!n.valid)throw integrity("MCP resource integrity failed",`violations=${n.violations.join(",")}`);if(!isPlainObject(o.resource.config))throw integrity("MCP authority payload must contain public object config","config_not_object");const a=void 0===o.resource.credential?void 0:credentialMaterial(o.resource.credential);return{descriptor:e,config:Object.freeze({...o.resource.config}),credential:a}}async redeemSkill(e){const t=this.bindingFor("skill",e.artifactRef);if(t.version!==e.version||t.sha256!==e.sha256||t.sizeBytes!==e.sizeBytes)throw resolution("skill descriptor does not match the immutable authority binding","descriptor_binding_mismatch");const{request:i,reservation:r}=this.requestFor(t),s=await this.redeem(t,i),o=new Uint8Array(await s.arrayBuffer()),n=skillHeaders(s),a=validateRuntimeSkillResourceRedemptionCompletion({request:i,binding:t,reservation:r,expectedDisposition:n["X-BeeOS-Redemption-Disposition"],headers:n,chunks:[o],sha256Utf8:sha256Utf8,sha256Bytes:sha256Bytes});if(!a.valid)throw integrity("skill resource integrity failed",`violations=${a.violations.join(",")} bytes=${o.byteLength}`);return{descriptor:e,parsed:await this.options.parseSkillArtifact(o)}}bindingFor(e,t){const i=this.byRef.get(t);if(!i||i.kind!==e||i.operationId!==this.options.operationId||i.instanceId!==this.options.instanceId||i.requestMessageId!==this.options.requestMessageId||i.paramsHash!==this.options.paramsHash)throw resolution("resource authority binding mismatch",`kind=${e} ${i?i.kind!==e?"field=kind":i.operationId!==this.options.operationId?"field=operationId":i.instanceId!==this.options.instanceId?"field=instanceId":i.requestMessageId!==this.options.requestMessageId?"field=requestMessageId":"field=paramsHash":"resourceRef=unbound"}`);return i}requestFor(e){const t=this.options.getAuthorization(),i=this.attempts.get(e.resourceRef),r=i?.redemptionId??(this.options.redemptionId?this.options.redemptionId():stableRedemptionId(e)),s={redemptionId:r,operationId:this.options.operationId,requestMessageId:this.options.requestMessageId,kind:e.kind,resourceRef:e.resourceRef,paramsHash:this.options.paramsHash,executionGrant:t.executionGrant,runtimeLeaseCredential:t.runtimeLeaseCredential,fence:t.fence},o=normalizeRuntimeResourceRedemptionHashPayload(s,e);if(!o)throw resolution("resource redemption request does not match immutable binding","request_binding_mismatch");const n=hashRuntimeResourceRedemptionPayload(o,sha256Utf8);if(i&&i.requestHash!==n)throw resolution("resource redemption intent changed","redemption_intent_changed");return this.attempts.set(e.resourceRef,{redemptionId:r,requestHash:n}),{request:s,reservation:{state:"reserved",redemptionId:r,redemptionRequestHash:n}}}async redeem(e,t){let i;try{i=await this.options.gateway.runtime.redeemResource(e.operationId,e.kind,e.resourceRef,t,this.options.signal?{signal:this.options.signal}:void 0)}catch{throw transient("runtime resource authority is unavailable","fetch_rejected")}return i.ok||await throwRedemptionFailure(i,e.kind),i}}function skillHeaders(e){const t=t=>{const i=e.headers.get(t);if(!i)throw integrity(`skill authority response omitted ${t}`,`missing_header=${t}`);return i};return{"X-BeeOS-Redemption-Id":t("X-BeeOS-Redemption-Id"),"X-BeeOS-Redemption-Disposition":t("X-BeeOS-Redemption-Disposition"),"X-BeeOS-Artifact-Ref":t("X-BeeOS-Artifact-Ref"),"X-BeeOS-Artifact-Version":t("X-BeeOS-Artifact-Version"),"X-BeeOS-Artifact-SHA256":t("X-BeeOS-Artifact-SHA256"),"X-BeeOS-Artifact-Size":t("X-BeeOS-Artifact-Size")}}function sha256Utf8(e){return createHash("sha256").update(e).digest("hex")}function stableRedemptionId(e){return`redemption:${sha256Utf8(`${e.operationId}\n${e.requestMessageId}\n${e.kind}\n${e.resourceRef}`)}`}function sha256Bytes(e){const t=createHash("sha256");for(const i of e)t.update(i);return t.digest("hex")}function sameMcpDescriptor(e,t){return e.definitionRef===t.definitionRef&&e.version===t.version&&e.sha256===t.sha256}function sameArtifactDescriptor(e,t){return e.artifactRef===t.artifactRef&&e.version===t.version&&e.sha256===t.sha256&&e.sizeBytes===t.sizeBytes&&e.source===t.source}function isPlainObject(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)&&(Object.getPrototypeOf(e)===Object.prototype||null===Object.getPrototypeOf(e))}function credentialMaterial(e){if(!isPlainObject(e)||Object.keys(e).some(e=>"headers"!==e&&"env"!==e))throw integrity("MCP credential payload is invalid","credential_payload_invalid");const t=stringMap(e.headers,"MCP credential headers"),i=stringMap(e.env,"MCP credential environment");if(!t&&!i)throw integrity("MCP credential payload is empty","credential_payload_empty");return Object.freeze({...t?{headers:Object.freeze(t)}:{},...i?{env:Object.freeze(i)}:{}})}function stringMap(e,t){if(void 0!==e){if(!isPlainObject(e)||Object.entries(e).some(([e,t])=>!e||e.length>256||/[\u0000-\u001f\u007f]/.test(e)||"string"!=typeof t||t.length>16384))throw integrity(`${t} are invalid`,"credential_string_map_invalid");return{...e}}}async function throwRedemptionFailure(e,t){if(408===e.status||425===e.status||429===e.status||e.status>=500)throw transient(`runtime ${t} redemption is temporarily unavailable`,`http_status=${e.status}`);let i;try{i=await e.json()}catch{throw integrity(`runtime ${t} redemption error is invalid`,`http_status=${e.status} error_body=not_json`)}const r=redemptionError(i);if(!r)throw integrity(`runtime ${t} redemption error is invalid`,`http_status=${e.status} error_body=unrecognized_shape`);throw new RuntimeResourceResolutionError("resolution_rejected",`runtime ${t} redemption was rejected`,r.code,`http_status=${e.status} code=${r.code}`)}function redemptionError(e){if(!isPlainObject(e)||"string"!=typeof e.status||"string"!=typeof e.code)return;if("fenced"===e.status&&"FENCED"===e.code&&"string"==typeof e.activeRuntimeEpoch&&/^(0|[1-9][0-9]*)$/.test(e.activeRuntimeEpoch)&&exact(e,["status","code","activeRuntimeEpoch"]))return e;return[["expired","EXPIRED"],["resource_not_found","RESOURCE_NOT_FOUND"],["redemption_conflict","REDEMPTION_CONFLICT"],["resource_integrity_failed","RESOURCE_INTEGRITY_FAILED"],["unauthorized","UNAUTHORIZED"]].some(([t,i])=>e.status===t&&e.code===i&&exact(e,["status","code"]))?e:void 0}function exact(e,t){const i=Object.keys(e).sort(),r=[...t].sort();return i.length===r.length&&i.every((e,t)=>e===r[t])}function resolution(e,t){return new RuntimeResourceResolutionError("resolution_rejected",e,void 0,t)}function integrity(e,t){return new RuntimeResourceResolutionError("integrity_mismatch",e,void 0,t)}function transient(e,t){return new RuntimeResourceResolutionError("transient",e,"TRANSIENT",t)}
1
+ import{createHash}from"node:crypto";import{hashRuntimeResourceRedemptionPayload,normalizeRuntimeResourceRedemptionHashPayload,validateRuntimeMcpResourceRedemptionCompletion,validateRuntimeSkillResourceRedemptionCompletion}from"@beeos-ai/beeos-types/runtime";import{parseRfc3339Instant}from"../acp-gateway/mcp-desired-state-store.js";export class RuntimeResourceResolutionError extends Error{kind;code;diagnostic;constructor(e,t,i,r){super(r?`${t} [${r}]`:t),this.kind=e,this.code=i,this.diagnostic=r,this.name="RuntimeResourceResolutionError"}}export class HttpRuntimeOperationResourceResolver{options;attempts=new Map;byRef;mcp;skill;constructor(e){this.options=e;const t=e.bindings.map(e=>[e.resourceRef,Object.freeze({...e})]);if(new Set(t.map(([e])=>e)).size!==t.length)throw new Error("duplicate resource binding");this.byRef=new Map(t)}async readContent(){throw new Error("Service inline content is forbidden; use a trusted artifact reference")}async resolveMcpDefinition(e){this.mcp||(this.mcp=this.redeemMcp(e).catch(e=>{throw this.mcp=void 0,e}));const t=await this.mcp;if(!sameMcpDescriptor(t.descriptor,e))throw new Error("MCP resource descriptor mismatch");return t.config}async resolveMcpCredential(e){this.mcp||(this.mcp=this.redeemMcp(e).catch(e=>{throw this.mcp=void 0,e}));const t=await this.mcp;if(!sameMcpDescriptor(t.descriptor,e))throw new Error("MCP resource descriptor mismatch");return t.credential}async readVerifiedArtifact(e){this.skill||(this.skill=this.redeemSkill(e).catch(e=>{throw this.skill=void 0,e}));const t=await this.skill;if(!sameArtifactDescriptor(t.descriptor,e))throw new Error("skill resource descriptor mismatch");return t.parsed}async redeemMcp(e){const t=this.bindingFor("mcp",e.definitionRef);if(t.version!==e.version||t.sha256!==e.sha256)throw resolution("MCP descriptor does not match the immutable authority binding","descriptor_binding_mismatch");const{request:i,reservation:r}=this.requestFor(t),s=await this.redeem(t,i);let n;try{n=await s.json()}catch{throw integrity("MCP authority response is invalid","response_not_json")}const o=validateRuntimeMcpResourceRedemptionCompletion({request:i,binding:t,reservation:r,expectedDisposition:n.disposition,result:n,sha256Utf8:sha256Utf8});if(!o.valid)throw integrity("MCP resource integrity failed",`violations=${o.violations.join(",")}`);if(!isPlainObject(n.resource.config))throw integrity("MCP authority payload must contain public object config","config_not_object");const a=void 0===n.resource.credential?void 0:credentialMaterial(n.resource.credential);if(a&&!a.expiresAt&&this.options.requireCredentialExpiry)throw integrity("MCP credential has no expiry","credential_expiry_missing");return{descriptor:e,config:Object.freeze({...n.resource.config}),credential:a}}async redeemSkill(e){const t=this.bindingFor("skill",e.artifactRef);if(t.version!==e.version||t.sha256!==e.sha256||t.sizeBytes!==e.sizeBytes)throw resolution("skill descriptor does not match the immutable authority binding","descriptor_binding_mismatch");const{request:i,reservation:r}=this.requestFor(t),s=await this.redeem(t,i),n=new Uint8Array(await s.arrayBuffer()),o=skillHeaders(s),a=validateRuntimeSkillResourceRedemptionCompletion({request:i,binding:t,reservation:r,expectedDisposition:o["X-BeeOS-Redemption-Disposition"],headers:o,chunks:[n],sha256Utf8:sha256Utf8,sha256Bytes:sha256Bytes});if(!a.valid)throw integrity("skill resource integrity failed",`violations=${a.violations.join(",")} bytes=${n.byteLength}`);return{descriptor:e,parsed:await this.options.parseSkillArtifact(n)}}bindingFor(e,t){const i=this.byRef.get(t);if(!i||i.kind!==e||i.operationId!==this.options.operationId||i.instanceId!==this.options.instanceId||i.requestMessageId!==this.options.requestMessageId||i.paramsHash!==this.options.paramsHash)throw resolution("resource authority binding mismatch",`kind=${e} ${i?i.kind!==e?"field=kind":i.operationId!==this.options.operationId?"field=operationId":i.instanceId!==this.options.instanceId?"field=instanceId":i.requestMessageId!==this.options.requestMessageId?"field=requestMessageId":"field=paramsHash":"resourceRef=unbound"}`);return i}requestFor(e){const t=this.options.getAuthorization(),i=this.attempts.get(e.resourceRef),r=i?.redemptionId??(this.options.redemptionId?this.options.redemptionId():stableRedemptionId(e)),s={redemptionId:r,operationId:this.options.operationId,requestMessageId:this.options.requestMessageId,kind:e.kind,resourceRef:e.resourceRef,paramsHash:this.options.paramsHash,executionGrant:t.executionGrant,runtimeLeaseCredential:t.runtimeLeaseCredential,fence:t.fence},n=normalizeRuntimeResourceRedemptionHashPayload(s,e);if(!n)throw resolution("resource redemption request does not match immutable binding","request_binding_mismatch");const o=hashRuntimeResourceRedemptionPayload(n,sha256Utf8);if(i&&i.requestHash!==o)throw resolution("resource redemption intent changed","redemption_intent_changed");return this.attempts.set(e.resourceRef,{redemptionId:r,requestHash:o}),{request:s,reservation:{state:"reserved",redemptionId:r,redemptionRequestHash:o}}}async redeem(e,t){let i;try{i=await this.options.gateway.runtime.redeemResource(e.operationId,e.kind,e.resourceRef,t,this.options.signal?{signal:this.options.signal}:void 0)}catch{throw transient("runtime resource authority is unavailable","fetch_rejected")}return i.ok||await throwRedemptionFailure(i,e.kind),i}}function skillHeaders(e){const t=t=>{const i=e.headers.get(t);if(!i)throw integrity(`skill authority response omitted ${t}`,`missing_header=${t}`);return i};return{"X-BeeOS-Redemption-Id":t("X-BeeOS-Redemption-Id"),"X-BeeOS-Redemption-Disposition":t("X-BeeOS-Redemption-Disposition"),"X-BeeOS-Artifact-Ref":t("X-BeeOS-Artifact-Ref"),"X-BeeOS-Artifact-Version":t("X-BeeOS-Artifact-Version"),"X-BeeOS-Artifact-SHA256":t("X-BeeOS-Artifact-SHA256"),"X-BeeOS-Artifact-Size":t("X-BeeOS-Artifact-Size")}}function sha256Utf8(e){return createHash("sha256").update(e).digest("hex")}function stableRedemptionId(e){return`redemption:${sha256Utf8(`${e.operationId}\n${e.requestMessageId}\n${e.kind}\n${e.resourceRef}`)}`}function sha256Bytes(e){const t=createHash("sha256");for(const i of e)t.update(i);return t.digest("hex")}function sameMcpDescriptor(e,t){return e.definitionRef===t.definitionRef&&e.version===t.version&&e.sha256===t.sha256}function sameArtifactDescriptor(e,t){return e.artifactRef===t.artifactRef&&e.version===t.version&&e.sha256===t.sha256&&e.sizeBytes===t.sizeBytes&&e.source===t.source}function isPlainObject(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)&&(Object.getPrototypeOf(e)===Object.prototype||null===Object.getPrototypeOf(e))}function credentialMaterial(e){if(!isPlainObject(e)||Object.keys(e).some(e=>"headers"!==e&&"env"!==e&&"expiresAt"!==e))throw integrity("MCP credential payload is invalid","credential_payload_invalid");const t=stringMap(e.headers,"MCP credential headers"),i=stringMap(e.env,"MCP credential environment");if(!t&&!i)throw integrity("MCP credential payload is empty","credential_payload_empty");const r=credentialExpiry(e.expiresAt);return Object.freeze({...t?{headers:Object.freeze(t)}:{},...i?{env:Object.freeze(i)}:{},...r?{expiresAt:r}:{}})}function credentialExpiry(e){if(void 0===e)return;const t=parseRfc3339Instant(e);if(!t)throw integrity("MCP credential expiry is invalid","credential_expiry_invalid");return t}function stringMap(e,t){if(void 0!==e){if(!isPlainObject(e)||Object.entries(e).some(([e,t])=>!e||e.length>256||/[\u0000-\u001f\u007f]/.test(e)||"string"!=typeof t||t.length>16384))throw integrity(`${t} are invalid`,"credential_string_map_invalid");return{...e}}}async function throwRedemptionFailure(e,t){if(408===e.status||425===e.status||429===e.status||e.status>=500)throw transient(`runtime ${t} redemption is temporarily unavailable`,`http_status=${e.status}`);let i;try{i=await e.json()}catch{throw integrity(`runtime ${t} redemption error is invalid`,`http_status=${e.status} error_body=not_json`)}const r=redemptionError(i);if(!r)throw integrity(`runtime ${t} redemption error is invalid`,`http_status=${e.status} error_body=unrecognized_shape`);throw new RuntimeResourceResolutionError("resolution_rejected",`runtime ${t} redemption was rejected`,r.code,`http_status=${e.status} code=${r.code}`)}function redemptionError(e){if(!isPlainObject(e)||"string"!=typeof e.status||"string"!=typeof e.code)return;if("fenced"===e.status&&"FENCED"===e.code&&"string"==typeof e.activeRuntimeEpoch&&/^(0|[1-9][0-9]*)$/.test(e.activeRuntimeEpoch)&&exact(e,["status","code","activeRuntimeEpoch"]))return e;return[["expired","EXPIRED"],["resource_not_found","RESOURCE_NOT_FOUND"],["redemption_conflict","REDEMPTION_CONFLICT"],["resource_integrity_failed","RESOURCE_INTEGRITY_FAILED"],["unauthorized","UNAUTHORIZED"]].some(([t,i])=>e.status===t&&e.code===i&&exact(e,["status","code"]))?e:void 0}function exact(e,t){const i=Object.keys(e).sort(),r=[...t].sort();return i.length===r.length&&i.every((e,t)=>e===r[t])}function resolution(e,t){return new RuntimeResourceResolutionError("resolution_rejected",e,void 0,t)}function integrity(e,t){return new RuntimeResourceResolutionError("integrity_mismatch",e,void 0,t)}function transient(e,t){return new RuntimeResourceResolutionError("transient",e,"TRANSIENT",t)}
@@ -1 +1 @@
1
- import{createHash,randomUUID}from"node:crypto";import{join}from"node:path";import{CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES,RUNTIME_METHOD_PARITY_MANIFEST,RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION,RUNTIME_RPC_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{FileRuntimeCommandJournal}from"./runtime-command-journal.js";import{FileRuntimeAgentIdentityRegistry}from"./runtime-agent-identity-registry.js";import{RuntimeRegistrationIdentityStore}from"./runtime-registration-identity-store.js";import{InstanceAuthorityRuntimeRegistrationTransport,RuntimeRegistrationCoordinator,RuntimeRegistrationJournalAdvanceRequiredError,signAgentMessage}from"@beeos-ai/cloud-harness-sdk";import{verifiedRuntimeParityManifestDigest}from"./runtime-parity-manifest-digest.js";import{intersectRuntimeServiceMethodsWithBackendCapabilities}from"./runtime-service-contract-compatibility.js";export function cloudRuntimeTargetInstanceId(t){if(void 0!==t){if(!/^[A-Za-z0-9_-]{1,128}$/.test(t))throw new Error("Cloud runtime target must match the provisioned BeeOS instance identity");return t}}export class RuntimeProductionAuthority{options;journal;identities;identityStore;handlerIdentity;listeners=new Set;coordinator;unsubscribeCoordinator;starting;rolling=new Map;refreshing;stoppedCoordinators=new WeakSet;coordinatorGeneration=0;lineage;identity;preparing;runtimeConsumerLive=!1;runtimeServiceMethods=Object.freeze([]);realtimeCapabilityReady=new Map;resourceBindingContractRevision;manifestDigest=verifiedRuntimeParityManifestDigest();started=!1;lifecycleVersion=0;authorityLeasePending=!1;constructor(t){if(this.options=t,void 0!==t.targetInstanceId&&!/^[A-Za-z0-9_-]{1,128}$/.test(t.targetInstanceId))throw new Error("Cloud runtime target instance ID is invalid");this.handlerIdentity=t.handlerIdentity??(process.env.HOSTNAME?.trim()||process.env.POD_NAME?.trim()||randomUUID()),this.journal=new FileRuntimeCommandJournal(join(t.stateDirectory,"runtime-command-journal.json"),void 0,void 0,{onMaintenanceError:e=>{const i=`[runtime-journal] event=checkpoint_failed trigger=threshold error=${e instanceof Error?e.message:String(e)}`;t.logger?.error?t.logger.error(i):t.logger?.warn?.(i)}}),this.identities=new FileRuntimeAgentIdentityRegistry(join(t.stateDirectory,"runtime-agent-identities.json")),this.identityStore=new RuntimeRegistrationIdentityStore(join(t.stateDirectory,"runtime-registration-identity.json"))}get agentKeys(){return this.options.keys}get stateDirectory(){return this.options.stateDirectory}get cloudTargetInstanceId(){return this.options.targetInstanceId}bindCloudTarget(t){if(void 0!==this.options.targetInstanceId){if(this.options.targetInstanceId!==t)throw new Error("Cloud runtime authority target changed during process lifetime")}else this.options.targetInstanceId=t}get authorityLease(){return this.coordinator?.authorityLease??null}subscribeAuthority(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}currentConsumerBinding(){return this.identity&&this.lineage?this.consumerBinding(this.identity,this.lineage):null}async prepareConsumerBinding(){return this.preparing||(this.preparing=(async()=>{const t=await this.identityStore.acquire(this.handlerIdentity,this.journal),e={journalStoreId:t.journalStoreId,journalGeneration:t.journalGeneration};return this.lineage=e,this.identity=t,this.consumerBinding(t,e)})().finally(()=>{this.preparing=void 0})),await this.preparing}setRuntimeConsumerLive(t){if(this.started)throw new Error("runtime consumer availability is immutable while registered");this.runtimeConsumerLive=t}setRuntimeServiceMethods(t){if(this.started)throw new Error("runtime Service methods are immutable while registered");this.runtimeServiceMethods=intersectRuntimeServiceMethodsWithBackendCapabilities(t)}setRuntimeRealtimeCapabilityReady(t,e){const i=this.realtimeCapabilityReady.get(t)!==e;if(this.realtimeCapabilityReady.set(t,e),!i||!this.started)return;const r=this.lifecycleVersion;this.refreshRealtimeCapabilities(r).catch(t=>this.options.logger?.warn?.(`[runtime-authority] authority lease refresh fenced (realtime capability): ${t instanceof Error?t.message:t}`))}setRuntimeResourceBindingContractRevision(t){if(this.started)throw new Error("runtime resource binding revision is immutable while registered");if(void 0!==t&&t!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION)throw new Error("unsupported runtime resource binding revision");this.resourceBindingContractRevision=t}async start(t){if(this.starting)return await this.starting;if(this.started)return;if(t?.aborted)return;const e=this.lifecycleVersion,i=this.startOnce(e,t).finally(()=>{this.starting===i&&(this.starting=void 0)});this.starting=i,await i}stop(){this.lifecycleVersion++,this.starting=void 0,this.started=!1;const t=this.coordinator;t&&this.detachCoordinator(t),this.publish(null)}async startOnce(t,e){if(await this.prepareConsumerBinding(),t!==this.lifecycleVersion||e?.aborted)return;this.lineage;const i=this.identity;this.started=!0,await this.activate(i,t)}async activate(t,e){if(!this.isLifecycleCurrent(e))return;if(this.resourceBindingContractRevision&&!this.runtimeConsumerLive)throw new Error("resource binding revision requires a live Message Service consumer");if(!this.runtimeConsumerLive||0===this.runtimeServiceMethods.length)throw new Error(`runtime registration refused: consumerLive=${this.runtimeConsumerLive} methods=${this.runtimeServiceMethods.length}`);const i=[...this.runtimeServiceMethods],r=runtimeCapabilities(this.runtimeServiceMethods,this.realtimeCapabilityReady);this.options.logger?.info?.(`[runtime-authority] advertising ${i.length} methods (renew-safe)`);const n=++this.coordinatorGeneration,o=new RuntimeRegistrationCoordinator(new InstanceAuthorityRuntimeRegistrationTransport({gateway:this.options.gateway,registrationId:t.registrationId}),{registrationId:t.registrationId,handlerIdentity:t.handlerIdentity,contractRevision:RUNTIME_RPC_CONTRACT_REVISION,runtimeMethods:i,capabilities:r,manifestDigest:this.manifestDigest,journalStoreId:t.journalStoreId,journalGeneration:t.journalGeneration,instanceIdentityKeyId:createHash("sha256").update(this.options.keys.publicKey).digest("hex"),targetInstanceId:this.options.targetInstanceId,signIdentityProof:t=>signAgentMessage(t,this.options.keys.privateKey),getMSConnectionStatus:this.options.getMSConnectionStatus},(t,i)=>{if(!this.isCoordinatorCurrent(e,n,o))return;const r=i instanceof Error?i.message:void 0!==i?String(i):"";this.options.logger?.warn?.(r?`[runtime-authority] lease lost: ${t}: ${r}`:`[runtime-authority] lease lost: ${t}`);const s=i instanceof RuntimeRegistrationJournalAdvanceRequiredError?i:void 0;("expired"===t||s)&&this.rollExpired(e,n,o,s).catch(t=>this.options.logger?.warn?.(`[runtime-authority] authority lease roll fenced (expired registration): ${t instanceof Error?t.message:t}`))});this.coordinator=o,this.unsubscribeCoordinator=o.subscribeAuthority(t=>{this.isCoordinatorCurrent(e,n,o)&&(t&&this.authorityLeasePending?(this.authorityLeasePending=!1,this.options.logger?.info?.("[runtime-authority] registration active (recovered after handoff)")):t||(this.authorityLeasePending=!0),this.publish(t))});const s=await o.start();this.isCoordinatorCurrent(e,n,o)?(this.options.logger?.info?.("[runtime-authority] registration "+(s?"active":"pending")),s||(this.authorityLeasePending=!0)):this.stopCoordinatorOnce(o)}async rollExpired(t,e,i,r){if(!this.lineage||!this.isCoordinatorCurrent(t,e,i))return;const n=this.rolling.get(e);if(n)return await n;let o=!1,s=!1;const a=(async()=>{if(!this.isCoordinatorCurrent(t,e,i))return;this.detachCoordinator(i),o=!0;const n=this.identity;if(!n)return;let a,h=0;for(;;){if(!this.isLifecycleCurrent(t)||void 0!==this.coordinator)return;try{a=await this.identityStore.roll(n,this.journal,{authorityCurrentGeneration:r?.currentJournalGeneration,expectedJournalGeneration:r?.expectedJournalGeneration});break}catch(t){if(!isRetryableRegistrationRollError(t))throw t;s||(this.publishDetachedWithoutLease("expired registration roll retry"),s=!0),this.options.logger?.warn?.(`[runtime-authority] retrying durable registration roll: ${t instanceof Error?t.message:t}`),await new Promise(t=>setTimeout(t,Math.min(250*2**Math.min(h++,4),4e3)))}}this.isLifecycleCurrent(t)&&void 0===this.coordinator&&(this.identity=a,this.lineage={journalStoreId:a.journalStoreId,journalGeneration:a.journalGeneration},await this.activate(a,t))})().finally(()=>{this.rolling.get(e)===a&&this.rolling.delete(e),o&&!s&&this.publishDetachedWithoutLease("expired registration roll")});this.rolling.set(e,a),await a}async refreshRealtimeCapabilities(t){if(!this.isLifecycleCurrent(t))return;const e=this.refreshing;if(e)return await e;let i=!1;const r=(async()=>{if(!this.isLifecycleCurrent(t))return;const e=this.identity,r=this.coordinator;e&&r&&(r.authorityLease?(this.detachCoordinator(r),i=!0,this.isLifecycleCurrent(t)&&void 0===this.coordinator&&await this.activate(e,t)):this.options.logger?.info?.("[runtime-authority] deferring realtime capability refresh until registration is active"))})().finally(()=>{this.refreshing===r&&(this.refreshing=void 0),i&&this.publishDetachedWithoutLease("realtime capability refresh")});this.refreshing=r,await r}isLifecycleCurrent(t){return this.started&&t===this.lifecycleVersion}isCoordinatorCurrent(t,e,i){return this.isLifecycleCurrent(t)&&this.coordinatorGeneration===e&&this.coordinator===i}detachCoordinator(t){this.coordinator===t&&(this.unsubscribeCoordinator?.(),this.unsubscribeCoordinator=void 0,this.coordinator=void 0),this.stopCoordinatorOnce(t)}publishDetachedWithoutLease(t){null===this.authorityLease&&(this.authorityLeasePending=!0,this.options.logger?.warn?.(`[runtime-authority] authority lease cleared with no live successor lease (${t})`),this.publish(null))}stopCoordinatorOnce(t){this.stoppedCoordinators.has(t)||(this.stoppedCoordinators.add(t),t.stop())}publish(t){for(const e of this.listeners)try{e(t)}catch{}}consumerBinding(t,e){return Object.freeze({handlerIdentity:t.handlerIdentity,journalStoreId:e.journalStoreId,journalGeneration:e.journalGeneration})}}const REALTIME_CAPABILITY_NAMES=["terminal","canvas"];function isRetryableRegistrationRollError(t){return t instanceof Error&&(t.message.includes("owned directory lock lease expired")||t.message.includes("owned directory lock was fenced")||t.message.includes("timed out acquiring lock")||t.message.includes("owned directory lock is busy"))}function runtimeCapabilities(t,e){const i=new Set(t),r=RUNTIME_METHOD_PARITY_MANIFEST.filter(t=>t.serviceAllowed&&void 0!==runtimeInvocationMethod(t)&&i.has(runtimeInvocationMethod(t))).map(t=>t.capability),n=REALTIME_CAPABILITY_NAMES.filter(t=>!0===e.get(t));return[...new Set([...r,...n])]}function runtimeInvocationMethod(t){return"methods"===t.serviceRoute&&t.serviceMethod?t.serviceMethod:Object.entries(CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES).find(([,e])=>e===t.canonicalCommand)?.[0]}
1
+ import{createHash,randomUUID}from"node:crypto";import{join}from"node:path";import{CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES,RUNTIME_METHOD_PARITY_MANIFEST,RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION,RUNTIME_RPC_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{FileRuntimeCommandJournal}from"./runtime-command-journal.js";import{FileRuntimeAgentIdentityRegistry}from"./runtime-agent-identity-registry.js";import{RuntimeRegistrationIdentityStore}from"./runtime-registration-identity-store.js";import{InstanceAuthorityRuntimeRegistrationTransport,RuntimeRegistrationCoordinator,RuntimeRegistrationJournalAdvanceRequiredError,signAgentMessage}from"@beeos-ai/cloud-harness-sdk";import{verifiedRuntimeParityManifestDigest}from"./runtime-parity-manifest-digest.js";import{intersectRuntimeServiceMethodsWithBackendCapabilities}from"./runtime-service-contract-compatibility.js";export function cloudRuntimeTargetInstanceId(t){if(void 0!==t){if(!/^[A-Za-z0-9_-]{1,128}$/.test(t))throw new Error("Cloud runtime target must match the provisioned BeeOS instance identity");return t}}export class RuntimeProductionAuthority{options;journal;identities;identityStore;handlerIdentity;listeners=new Set;coordinator;unsubscribeCoordinator;starting;rolling=new Map;refreshing;stoppedCoordinators=new WeakSet;coordinatorGeneration=0;lineage;identity;preparing;runtimeConsumerLive=!1;runtimeServiceMethods=Object.freeze([]);realtimeCapabilityReady=new Map;resourceBindingContractRevision;manifestDigest=verifiedRuntimeParityManifestDigest();started=!1;lifecycleVersion=0;authorityLeasePending=!1;constructor(t){if(this.options=t,void 0!==t.targetInstanceId&&!/^[A-Za-z0-9_-]{1,128}$/.test(t.targetInstanceId))throw new Error("Cloud runtime target instance ID is invalid");this.handlerIdentity=t.handlerIdentity??(process.env.HOSTNAME?.trim()||process.env.POD_NAME?.trim()||randomUUID()),this.journal=new FileRuntimeCommandJournal(join(t.stateDirectory,"runtime-command-journal.json"),void 0,void 0,{onMaintenanceError:e=>{const i=`[runtime-journal] event=checkpoint_failed trigger=threshold error=${e instanceof Error?e.message:String(e)}`;t.logger?.error?t.logger.error(i):t.logger?.warn?.(i)}}),this.identities=new FileRuntimeAgentIdentityRegistry(join(t.stateDirectory,"runtime-agent-identities.json")),this.identityStore=new RuntimeRegistrationIdentityStore(join(t.stateDirectory,"runtime-registration-identity.json"))}get agentKeys(){return this.options.keys}get stateDirectory(){return this.options.stateDirectory}get cloudTargetInstanceId(){return this.options.targetInstanceId}bindCloudTarget(t){if(void 0!==this.options.targetInstanceId){if(this.options.targetInstanceId!==t)throw new Error("Cloud runtime authority target changed during process lifetime")}else this.options.targetInstanceId=t}get authorityLease(){return this.coordinator?.authorityLease??null}subscribeAuthority(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}currentConsumerBinding(){return this.identity&&this.lineage?this.consumerBinding(this.identity,this.lineage):null}async prepareConsumerBinding(){return this.preparing||(this.preparing=(async()=>{const t=await this.identityStore.acquire(this.handlerIdentity,this.journal),e={journalStoreId:t.journalStoreId,journalGeneration:t.journalGeneration};return this.lineage=e,this.identity=t,this.consumerBinding(t,e)})().finally(()=>{this.preparing=void 0})),await this.preparing}setRuntimeConsumerLive(t){if(this.started)throw new Error("runtime consumer availability is immutable while registered");this.runtimeConsumerLive=t}setRuntimeServiceMethods(t){if(this.started)throw new Error("runtime Service methods are immutable while registered");this.runtimeServiceMethods=intersectRuntimeServiceMethodsWithBackendCapabilities(t)}setRuntimeRealtimeCapabilityReady(t,e){const i=this.realtimeCapabilityReady.get(t)!==e;if(this.realtimeCapabilityReady.set(t,e),!i||!this.started)return;const r=this.lifecycleVersion;this.refreshRealtimeCapabilities(r).catch(t=>this.options.logger?.warn?.(`[runtime-authority] authority lease refresh fenced (realtime capability): ${t instanceof Error?t.message:t}`))}setRuntimeResourceBindingContractRevision(t){if(this.started)throw new Error("runtime resource binding revision is immutable while registered");if(void 0!==t&&t!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION)throw new Error("unsupported runtime resource binding revision");this.resourceBindingContractRevision=t}async start(t){if(this.starting)return await this.starting;if(this.started)return;if(t?.aborted)return;const e=this.lifecycleVersion,i=this.startOnce(e,t).finally(()=>{this.starting===i&&(this.starting=void 0)});this.starting=i,await i}stop(){this.lifecycleVersion++,this.starting=void 0,this.started=!1;const t=this.coordinator;t&&this.detachCoordinator(t),this.publish(null)}async startOnce(t,e){if(await this.prepareConsumerBinding(),t!==this.lifecycleVersion||e?.aborted)return;this.lineage;const i=this.identity;this.started=!0,await this.activate(i,t)}async activate(t,e){if(!this.isLifecycleCurrent(e))return;if(this.resourceBindingContractRevision&&!this.runtimeConsumerLive)throw new Error("resource binding revision requires a live Message Service consumer");if(!this.runtimeConsumerLive||0===this.runtimeServiceMethods.length)throw new Error(`runtime registration refused: consumerLive=${this.runtimeConsumerLive} methods=${this.runtimeServiceMethods.length}`);const i=[...this.runtimeServiceMethods],r=runtimeCapabilities(this.runtimeServiceMethods,this.realtimeCapabilityReady);this.options.logger?.info?.(`[runtime-authority] advertising ${i.length} methods (renew-safe)`);const n=++this.coordinatorGeneration,o=new RuntimeRegistrationCoordinator(new InstanceAuthorityRuntimeRegistrationTransport({gateway:this.options.gateway,registrationId:t.registrationId}),{registrationId:t.registrationId,handlerIdentity:t.handlerIdentity,contractRevision:RUNTIME_RPC_CONTRACT_REVISION,runtimeMethods:i,capabilities:r,manifestDigest:this.manifestDigest,journalStoreId:t.journalStoreId,journalGeneration:t.journalGeneration,instanceIdentityKeyId:createHash("sha256").update(this.options.keys.publicKey).digest("hex"),targetInstanceId:this.options.targetInstanceId,signIdentityProof:t=>signAgentMessage(t,this.options.keys.privateKey),getMSConnectionStatus:this.options.getMSConnectionStatus},(t,i)=>{if(!this.isCoordinatorCurrent(e,n,o))return;const r=i instanceof Error?i.message:void 0!==i?String(i):"";this.options.logger?.warn?.(r?`[runtime-authority] lease lost: ${t}: ${r}`:`[runtime-authority] lease lost: ${t}`),"fenced"===t&&this.publish(null,"fenced");const s=i instanceof RuntimeRegistrationJournalAdvanceRequiredError?i:void 0;("expired"===t||s)&&this.rollExpired(e,n,o,s).catch(t=>this.options.logger?.warn?.(`[runtime-authority] authority lease roll fenced (expired registration): ${t instanceof Error?t.message:t}`))});this.coordinator=o,this.unsubscribeCoordinator=o.subscribeAuthority(t=>{this.isCoordinatorCurrent(e,n,o)&&(t&&this.authorityLeasePending?(this.authorityLeasePending=!1,this.options.logger?.info?.("[runtime-authority] registration active (recovered after handoff)")):t||(this.authorityLeasePending=!0),this.publish(t))});const s=await o.start();this.isCoordinatorCurrent(e,n,o)?(this.options.logger?.info?.("[runtime-authority] registration "+(s?"active":"pending")),s||(this.authorityLeasePending=!0)):this.stopCoordinatorOnce(o)}async rollExpired(t,e,i,r){if(!this.lineage||!this.isCoordinatorCurrent(t,e,i))return;const n=this.rolling.get(e);if(n)return await n;let o=!1,s=!1;const a=(async()=>{if(!this.isCoordinatorCurrent(t,e,i))return;this.detachCoordinator(i),o=!0;const n=this.identity;if(!n)return;let a,h=0;for(;;){if(!this.isLifecycleCurrent(t)||void 0!==this.coordinator)return;try{a=await this.identityStore.roll(n,this.journal,{authorityCurrentGeneration:r?.currentJournalGeneration,expectedJournalGeneration:r?.expectedJournalGeneration});break}catch(t){if(!isRetryableRegistrationRollError(t))throw t;s||(this.publishDetachedWithoutLease("expired registration roll retry"),s=!0),this.options.logger?.warn?.(`[runtime-authority] retrying durable registration roll: ${t instanceof Error?t.message:t}`),await new Promise(t=>setTimeout(t,Math.min(250*2**Math.min(h++,4),4e3)))}}this.isLifecycleCurrent(t)&&void 0===this.coordinator&&(this.identity=a,this.lineage={journalStoreId:a.journalStoreId,journalGeneration:a.journalGeneration},await this.activate(a,t))})().finally(()=>{this.rolling.get(e)===a&&this.rolling.delete(e),o&&!s&&this.publishDetachedWithoutLease("expired registration roll")});this.rolling.set(e,a),await a}async refreshRealtimeCapabilities(t){if(!this.isLifecycleCurrent(t))return;const e=this.refreshing;if(e)return await e;let i=!1;const r=(async()=>{if(!this.isLifecycleCurrent(t))return;const e=this.identity,r=this.coordinator;e&&r&&(r.authorityLease?(this.detachCoordinator(r),i=!0,this.isLifecycleCurrent(t)&&void 0===this.coordinator&&await this.activate(e,t)):this.options.logger?.info?.("[runtime-authority] deferring realtime capability refresh until registration is active"))})().finally(()=>{this.refreshing===r&&(this.refreshing=void 0),i&&this.publishDetachedWithoutLease("realtime capability refresh")});this.refreshing=r,await r}isLifecycleCurrent(t){return this.started&&t===this.lifecycleVersion}isCoordinatorCurrent(t,e,i){return this.isLifecycleCurrent(t)&&this.coordinatorGeneration===e&&this.coordinator===i}detachCoordinator(t){this.coordinator===t&&(this.unsubscribeCoordinator?.(),this.unsubscribeCoordinator=void 0,this.coordinator=void 0),this.stopCoordinatorOnce(t)}publishDetachedWithoutLease(t){null===this.authorityLease&&(this.authorityLeasePending=!0,this.options.logger?.warn?.(`[runtime-authority] authority lease cleared with no live successor lease (${t})`),this.publish(null))}stopCoordinatorOnce(t){this.stoppedCoordinators.has(t)||(this.stoppedCoordinators.add(t),t.stop())}publish(t,e){for(const i of this.listeners)try{i(t,e)}catch{}}consumerBinding(t,e){return Object.freeze({handlerIdentity:t.handlerIdentity,journalStoreId:e.journalStoreId,journalGeneration:e.journalGeneration})}}const REALTIME_CAPABILITY_NAMES=["terminal","canvas"];function isRetryableRegistrationRollError(t){return t instanceof Error&&(t.message.includes("owned directory lock lease expired")||t.message.includes("owned directory lock was fenced")||t.message.includes("timed out acquiring lock")||t.message.includes("owned directory lock is busy"))}function runtimeCapabilities(t,e){const i=new Set(t),r=RUNTIME_METHOD_PARITY_MANIFEST.filter(t=>t.serviceAllowed&&void 0!==runtimeInvocationMethod(t)&&i.has(runtimeInvocationMethod(t))).map(t=>t.capability),n=REALTIME_CAPABILITY_NAMES.filter(t=>!0===e.get(t));return[...new Set([...r,...n])]}function runtimeInvocationMethod(t){return"methods"===t.serviceRoute&&t.serviceMethod?t.serviceMethod:Object.entries(CONVERSATION_RUNTIME_CANONICAL_COMMAND_NAMES).find(([,e])=>e===t.canonicalCommand)?.[0]}
@@ -1 +1 @@
1
- import{RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{TemplatePackageError,parseAndValidateTemplatePackage}from"../acp-gateway/agent-template-package.js";import{HttpRuntimeOperationResourceResolver,RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{CloudGatewayGrantJwksVerifier,GatewayGrantJwksVerifier,RuntimeJwksVerifier,createCloudGrantVerifier,warmGrantJwks}from"@beeos-ai/cloud-harness-sdk";import{fetchRuntimeLeaseAttempt}from"./message-service-runtime-authenticated-fetch.js";import{MessageServiceConversationClearHttpAuthority}from"./message-service-conversation-clear-http-authority.js";import{MessageServiceConversationHistoryBoundary}from"./message-service-conversation-history-boundary.js";import{MessageServiceSessionModelHttpAuthority}from"./message-service-session-model-http-authority.js";import{MsRuntimeMessageChannelConsumerFactory}from"./ms-runtime-message-channel-consumer-factory.js";import{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,{gateway:e.gateway})}}export class AgentGatewaySplitJwksReadinessAdapter extends AgentGatewayGrantJwksReadinessAdapter{}const transportOptions=new WeakMap;export class RuntimeServiceTransportReadinessAdapter{constructor(e){transportOptions.set(this,{messageService:e.messageService,messageClient:e.messageClient,configuredServiceUrl:e.configuredServiceUrl,scopedDeliveryKey:e.scopedDeliveryKey,gateway:e.gateway})}}const cloudTransportOptions=new WeakMap;export class CloudRuntimeServiceTransportReadinessAdapter{constructor(e){cloudTransportOptions.set(this,{...e})}}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])};const r=e.cloudTransportAdapter?cloudTransportOptions.get(e.cloudTransportAdapter):void 0,t=e.authorizationAdapter?startGatewayGrantJwks(e.authorizationAdapter,Boolean(r),e.onGrantJwksWarm):void 0;if(r){if(!r.configuredServiceUrl)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!(t instanceof CloudGatewayGrantJwksVerifier))return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};const n=intersectRuntimeServiceMethodsWithBackendCapabilities(["models/list","agent/create","skills/list","skills/update","skills/uninstall","skills/install"]);return n.includes("models/list")?{status:"ready",blockers:[],capabilityBlockers:[],serviceMethods:n,factory:new MsRuntimeMessageChannelConsumerFactory({runtimeDelivery:r.runtimeDelivery,...r.nativePrompt?{nativePrompt:r.nativePrompt}:{},...r.sessionNewConversation?{sessionNewConversation:r.sessionNewConversation}:{},verifier:t,ports:e.attemptPorts??runtimeMessageAttemptPorts(r,r.configuredServiceUrl),journal:e.commandJournal,allowedMethods:new Set(n),options:{poisonJournal:e.poisonJournal,requestSchemaVersion:2,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerLeaseStarvation?{onLeaseStarvation:e.onConsumerLeaseStarvation}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{},...e.onConsumerRequestFailure?{onRequestFailure:e.onConsumerRequestFailure}:{}}})}:{status:"unavailable",blockers:Object.freeze([CONTRACT_ARTIFACT_BLOCKER])}}const n=e.transportAdapter?transportOptions.get(e.transportAdapter):void 0;if(!n?.messageClient||!n.configuredServiceUrl)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!t)return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};const a=runtimeMethods(),o=e.attemptPorts??runtimeMessageAttemptPorts(n,n.configuredServiceUrl);return{status:"ready",blockers:[],capabilityBlockers:CAPABILITY_BLOCKERS,serviceMethods:a,factory:new MsRuntimeMessageChannelConsumerFactory({client:n.messageClient,verifier:t,ports:o,journal:e.commandJournal,allowedMethods:new Set(a),options:{poisonJournal:e.poisonJournal,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerLeaseStarvation?{onLeaseStarvation:e.onConsumerLeaseStarvation}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{},...e.onConsumerRequestFailure?{onRequestFailure:e.onConsumerRequestFailure}:{}}}),...void 0===e.attemptPorts?{resourceBindingContractRevision:RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}:{}}}function startGatewayGrantJwks(e,r,t){const n=grantAdapterOptions.get(e);if(!n)throw new Error("untrusted Gateway grant readiness adapter");const a=n.gateway.baseUrl.toString().replace(/\/+$/,""),o=r?createCloudGrantVerifier(a):new GatewayGrantJwksVerifier(new RuntimeJwksVerifier(`${a}/.well-known/runtime-execution-grant-jwks.json`));return warmGrantJwks(o,{onRetry:({attempt:e,error:r})=>t?.({ready:!1,attempt:e,error:r})}).then(e=>t?.({ready:e})),o}export function runtimeMessageAttemptPorts(e,r){return{async resolve({request:t,command:n,executionGrant:a,getLease:o,journalStoreId:s,journalGeneration:i,journalRecord:c,abortSignal:u}){const d="resourceBindings"in t?t.resourceBindings:void 0,l="resourceBindingContractRevision"in t?t.resourceBindingContractRevision:void 0,m="skills.install"===n.name||"agent.applyTemplate"===n.name?n.params.artifact:void 0,p="mcp.prepare"===n.name||"mcp.set"===n.name?n.params.definition:void 0;if((m||p)&&(l!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||!d))throw new RuntimeResourceResolutionError("resolution_rejected","resource-bound Service delivery requires the Types 4.1 binding carrier",void 0,`binding_contract_revision=${l??"absent"} bindings=${d?"present":"absent"}`);const R=new HttpRuntimeOperationResourceResolver({gateway:e.gateway,operationId:t.operationId,requestMessageId:t.requestMessageId,instanceId:t.instanceId,paramsHash:t.paramsHash,bindings:d??[],getAuthorization:()=>{const e=o();return{executionGrant:a,runtimeLeaseCredential:e.runtimeLeaseCredential,fence:deliveryFence(t,e,s,i)}},parseSkillArtifact:e=>parseSkillPackage(e),signal:u});if("prepared"!==c.phase&&"started"!==c.phase||(m&&await R.readVerifiedArtifact(m),p&&await R.resolveMcpDefinition(p)),"session.setModel"===n.name){if(!e.messageService)throw new Error("Cloud runtime cannot project a personal session model");const n=e.scopedDeliveryKey;if(!n)throw new Error("session model requires X-Runtime-Delivery-Key");return{resources:R,sessionModelProjection:{project:async({conversationId:e,modelOverrideId:a})=>{const s=o(),i=new MessageServiceSessionModelHttpAuthority({messageServiceUrl:r,scopedDeliveryKey:n,fetchAuthorized:(e,r)=>fetchRuntimeLeaseAttempt(e,r??{})});return await i.project({conversationId:e,operationId:t.operationId,requestMessageId:t.requestMessageId,requestHash:t.requestHash,modelOverrideId:a,runtimeLeaseCredential:s.runtimeLeaseCredential})}}}}if("session.clear"!==n.name)return{resources:R};if(!e.messageService)throw new Error("Cloud runtime cannot access personal conversation history");n.context.conversation;const v=o(),C=new MessageServiceConversationClearHttpAuthority({messageServiceUrl:r,runtimeLeaseCredential:v.runtimeLeaseCredential,fetchAuthorized:(e,r)=>fetchRuntimeLeaseAttempt(e,r??{})});return{resources:R,historyBoundary:new MessageServiceConversationHistoryBoundary({operationId:t.operationId,requestMessageId:t.requestMessageId,requestHash:t.requestHash,fence:deliveryFence(t,v,s,i),authority:C})}}}}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 parseSkillPackage(e){try{return parseAndValidateTemplatePackage(Buffer.from(e))}catch(r){if(!(r instanceof TemplatePackageError))throw r;throw new RuntimeResourceResolutionError("resolution_rejected","skill package failed validation",void 0,`package_invalid bytes=${e.byteLength} reason=${r.message}`)}}function deliveryFence(e,r,t,n){if(r.handlerIdentity!==e.runtimeHandlerIdentity||r.runtimeEpoch!==e.dispatchEpoch)throw new Error("runtime delivery fence does not match the retained request");return{claimId:e.requestMessageId,handlerIdentity:r.handlerIdentity,runtimeEpoch:r.runtimeEpoch,fencingToken:r.runtimeEpoch,journalStoreId:t,journalGeneration:n}}
1
+ import{RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{TemplatePackageError,parseAndValidateTemplatePackage}from"../acp-gateway/agent-template-package.js";import{HttpRuntimeOperationResourceResolver,RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{CloudGatewayGrantJwksVerifier,GatewayGrantJwksVerifier,RuntimeJwksVerifier,createCloudGrantVerifier,warmGrantJwks}from"@beeos-ai/cloud-harness-sdk";import{fetchRuntimeLeaseAttempt}from"./message-service-runtime-authenticated-fetch.js";import{MessageServiceConversationClearHttpAuthority}from"./message-service-conversation-clear-http-authority.js";import{MessageServiceConversationHistoryBoundary}from"./message-service-conversation-history-boundary.js";import{MessageServiceSessionModelHttpAuthority}from"./message-service-session-model-http-authority.js";import{MsRuntimeMessageChannelConsumerFactory}from"./ms-runtime-message-channel-consumer-factory.js";import{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,{gateway:e.gateway})}}export class AgentGatewaySplitJwksReadinessAdapter extends AgentGatewayGrantJwksReadinessAdapter{}const transportOptions=new WeakMap;export class RuntimeServiceTransportReadinessAdapter{constructor(e){transportOptions.set(this,{messageService:e.messageService,messageClient:e.messageClient,configuredServiceUrl:e.configuredServiceUrl,scopedDeliveryKey:e.scopedDeliveryKey,gateway:e.gateway})}}const cloudTransportOptions=new WeakMap;export class CloudRuntimeServiceTransportReadinessAdapter{constructor(e){cloudTransportOptions.set(this,{...e})}}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])};const r=e.cloudTransportAdapter?cloudTransportOptions.get(e.cloudTransportAdapter):void 0,t=e.authorizationAdapter?startGatewayGrantJwks(e.authorizationAdapter,Boolean(r),e.onGrantJwksWarm):void 0;if(r){if(!r.configuredServiceUrl)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!(t instanceof CloudGatewayGrantJwksVerifier))return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};const n=intersectRuntimeServiceMethodsWithBackendCapabilities(["models/list","agent/create","skills/list","skills/update","skills/uninstall","skills/install","mcp/list","mcp/set","mcp/unset"]);return n.includes("models/list")?{status:"ready",blockers:[],capabilityBlockers:[],serviceMethods:n,factory:new MsRuntimeMessageChannelConsumerFactory({runtimeDelivery:r.runtimeDelivery,...r.nativePrompt?{nativePrompt:r.nativePrompt}:{},...r.sessionNewConversation?{sessionNewConversation:r.sessionNewConversation}:{},verifier:t,ports:e.attemptPorts??runtimeMessageAttemptPorts(r,r.configuredServiceUrl,{requireCredentialExpiry:!0}),journal:e.commandJournal,allowedMethods:new Set(n),options:{poisonJournal:e.poisonJournal,requestSchemaVersion:2,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerLeaseStarvation?{onLeaseStarvation:e.onConsumerLeaseStarvation}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{},...e.onConsumerRequestFailure?{onRequestFailure:e.onConsumerRequestFailure}:{}}})}:{status:"unavailable",blockers:Object.freeze([CONTRACT_ARTIFACT_BLOCKER])}}const n=e.transportAdapter?transportOptions.get(e.transportAdapter):void 0;if(!n?.messageClient||!n.configuredServiceUrl)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!t)return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};const a=runtimeMethods(),o=e.attemptPorts??runtimeMessageAttemptPorts(n,n.configuredServiceUrl);return{status:"ready",blockers:[],capabilityBlockers:CAPABILITY_BLOCKERS,serviceMethods:a,factory:new MsRuntimeMessageChannelConsumerFactory({client:n.messageClient,verifier:t,ports:o,journal:e.commandJournal,allowedMethods:new Set(a),options:{poisonJournal:e.poisonJournal,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerLeaseStarvation?{onLeaseStarvation:e.onConsumerLeaseStarvation}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{},...e.onConsumerRequestFailure?{onRequestFailure:e.onConsumerRequestFailure}:{}}}),...void 0===e.attemptPorts?{resourceBindingContractRevision:RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}:{}}}function startGatewayGrantJwks(e,r,t){const n=grantAdapterOptions.get(e);if(!n)throw new Error("untrusted Gateway grant readiness adapter");const a=n.gateway.baseUrl.toString().replace(/\/+$/,""),o=r?createCloudGrantVerifier(a):new GatewayGrantJwksVerifier(new RuntimeJwksVerifier(`${a}/.well-known/runtime-execution-grant-jwks.json`));return warmGrantJwks(o,{onRetry:({attempt:e,error:r})=>t?.({ready:!1,attempt:e,error:r})}).then(e=>t?.({ready:e})),o}export function runtimeMessageAttemptPorts(e,r,t={}){return{async resolve({request:n,command:a,executionGrant:o,getLease:s,journalStoreId:i,journalGeneration:c,journalRecord:u,abortSignal:d}){const l="resourceBindings"in n?n.resourceBindings:void 0,m="resourceBindingContractRevision"in n?n.resourceBindingContractRevision:void 0,p="skills.install"===a.name||"agent.applyTemplate"===a.name?a.params.artifact:void 0,R="mcp.prepare"===a.name||"mcp.set"===a.name?a.params.definition:void 0;if((p||R)&&(m!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||!l))throw new RuntimeResourceResolutionError("resolution_rejected","resource-bound Service delivery requires the Types 4.1 binding carrier",void 0,`binding_contract_revision=${m??"absent"} bindings=${l?"present":"absent"}`);const C=new HttpRuntimeOperationResourceResolver({gateway:e.gateway,operationId:n.operationId,requestMessageId:n.requestMessageId,instanceId:n.instanceId,paramsHash:n.paramsHash,bindings:l??[],getAuthorization:()=>{const e=s();return{executionGrant:o,runtimeLeaseCredential:e.runtimeLeaseCredential,fence:deliveryFence(n,e,i,c)}},parseSkillArtifact:e=>parseSkillPackage(e),signal:d,...t.requireCredentialExpiry?{requireCredentialExpiry:!0}:{}});if("prepared"!==u.phase&&"started"!==u.phase||(p&&await C.readVerifiedArtifact(p),R&&await C.resolveMcpDefinition(R)),"session.setModel"===a.name){if(!e.messageService)throw new Error("Cloud runtime cannot project a personal session model");const t=e.scopedDeliveryKey;if(!t)throw new Error("session model requires X-Runtime-Delivery-Key");return{resources:C,sessionModelProjection:{project:async({conversationId:e,modelOverrideId:a})=>{const o=s(),i=new MessageServiceSessionModelHttpAuthority({messageServiceUrl:r,scopedDeliveryKey:t,fetchAuthorized:(e,r)=>fetchRuntimeLeaseAttempt(e,r??{})});return await i.project({conversationId:e,operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,modelOverrideId:a,runtimeLeaseCredential:o.runtimeLeaseCredential})}}}}if("session.clear"!==a.name)return{resources:C};if(!e.messageService)throw new Error("Cloud runtime cannot access personal conversation history");a.context.conversation;const v=s(),y=new MessageServiceConversationClearHttpAuthority({messageServiceUrl:r,runtimeLeaseCredential:v.runtimeLeaseCredential,fetchAuthorized:(e,r)=>fetchRuntimeLeaseAttempt(e,r??{})});return{resources:C,historyBoundary:new MessageServiceConversationHistoryBoundary({operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,fence:deliveryFence(n,v,i,c),authority:y})}}}}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 parseSkillPackage(e){try{return parseAndValidateTemplatePackage(Buffer.from(e))}catch(r){if(!(r instanceof TemplatePackageError))throw r;throw new RuntimeResourceResolutionError("resolution_rejected","skill package failed validation",void 0,`package_invalid bytes=${e.byteLength} reason=${r.message}`)}}function deliveryFence(e,r,t,n){if(r.handlerIdentity!==e.runtimeHandlerIdentity||r.runtimeEpoch!==e.dispatchEpoch)throw new Error("runtime delivery fence does not match the retained request");return{claimId:e.requestMessageId,handlerIdentity:r.handlerIdentity,runtimeEpoch:r.runtimeEpoch,fencingToken:r.runtimeEpoch,journalStoreId:t,journalGeneration:n}}
@@ -1 +1 @@
1
- import{TERMINAL_MAX_FRAME_BYTES,TERMINAL_SERVICE_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/runtime";import{TerminalNotFoundError}from"./session-manager.js";import{getOrCreateRuntimeTerminalService}from"./runtime-terminal-service.js";import{TerminalAuthorityAdmission}from"./terminal-authority-admission.js";const DEFAULT_REPLAY_RETENTION_MS=3e5;export class RuntimeTerminalOutboundRegistration{options;service;authorityAdmission;serviceGeneration;rings=new Map;ringBytes=new Map;droppedThrough=new Map;pending=[];pendingBytes=0;activeSend;activeFlush;flushes=new Set;connected=!1;draining=!1;stopped=!1;eventGeneration=0;transportEpoch=0;drainPromise;closePromise;replayExpiryTimers=new Map;constructor(e){this.options=e,this.service=e.service??getOrCreateRuntimeTerminalService(e.manager),this.authorityAdmission=new TerminalAuthorityAdmission(e.authorityJournal,e.authorityClock),this.serviceGeneration=this.service.setOutbound(this);const t=this.eventGeneration;e.client.on("connected",()=>{this.acceptsEvent(t)&&(this.connected||(this.transportEpoch++,this.activeFlush=void 0,this.serviceGeneration??=this.service.setOutbound(this),this.connected=!0,this.publishAvailability(!0),this.kickFlush(),e.logger?.info?.("[terminal-service] outbound registration connected")))}),e.client.on("disconnected",()=>{this.acceptsEvent(t)&&this.invalidateConnection(!1)}),e.client.on("message",(...e)=>{if(!this.acceptsEvent(t))return;const i=this.serviceGeneration,n=this.transportEpoch;if(!i||!this.acceptsTransport(n,i))return;const s=e[0],r="string"==typeof s?s:Buffer.from(s).toString("utf8");this.handleWire(r,i,n)}),e.client.on("error",(...i)=>{this.acceptsEvent(t)&&e.logger?.warn?.("[terminal-service] outbound registration error",i[0])}),e.client.on("authorityLost",()=>{this.acceptsEvent(t)&&(this.invalidateConnection(!0),e.logger?.warn?.("[terminal-service] runtime lease lost; Service terminals fenced"))}),e.client.on("transportEpochChanged",()=>{this.acceptsEvent(t)&&(this.invalidateConnection(!0),e.logger?.info?.("[terminal-service] runtime lease rotated; prior Terminal epoch fenced"))})}async start(){if(this.stopped)throw new Error("terminal outbound registration is stopped");await this.options.client.connect()}isAvailable(){return this.connected&&!this.draining&&!this.stopped}send(e){if(this.stopped)return!1;const t=JSON.stringify(e),i=Buffer.byteLength(t);return this.remember(e,t,i),!this.connected||(this.enqueueWithOutputBackpressure({clientId:e.clientId,terminalId:e.terminalId,frame:e,wire:t,bytes:i}),this.kickFlush(),!0)}resume(e,t,i){const n=this.transportEpoch;if(!this.acceptsConnection(n))throw new Error("terminal outbound registration is disconnected");const s=BigInt(i),r=s<(this.droppedThrough.get(t)??0n),a=[];let o=-1n;for(const i of this.rings.get(t)??[]){if(i.frame.clientId!==e)throw new Error("terminal resume ownership mismatch");const n=BigInt(i.frame.sequence);if(n<=o)throw new Error("terminal replay sequence is not monotonic");o=n,n<=s||a.push({clientId:e,terminalId:t,frame:i.frame,wire:i.wire,bytes:i.bytes})}if(!this.acceptsConnection(n))throw new Error("terminal outbound registration epoch changed");for(const e of a)this.enqueueWithOutputBackpressure(e);return this.acknowledgeReplay(t,s),this.kickFlush(),{gap:r}}rebindResumeClient(e,t,i){if(e===t)return;const n=this.rings.get(i);if(n?.some(t=>t.frame.clientId!==e))throw new Error("terminal resume ownership mismatch");if(n){let e=0;const s=n.map(i=>{const n=Object.freeze({...i.frame,clientId:t}),s=JSON.stringify(n),r=Buffer.byteLength(s);return e+=r,{frame:n,wire:s,bytes:r}});this.rings.set(i,s),this.ringBytes.set(i,e)}for(let n=0;n<this.pending.length;n++){const s=this.pending[n];if(s.terminalId!==i||s.clientId!==e)continue;const r={...s.frame,clientId:t},a=JSON.stringify(r),o=Buffer.byteLength(a);this.pendingBytes+=o-s.bytes,this.pending[n]={clientId:t,terminalId:i,frame:r,wire:a,bytes:o}}if(this.activeSend?.item.terminalId===i&&this.activeSend.item.clientId===e){const e=this.activeSend.item,n={...e.frame,clientId:t},s=JSON.stringify(n),r=Buffer.byteLength(s);this.activeSend.counted&&(this.pendingBytes+=r-e.bytes),this.activeSend={item:{clientId:t,terminalId:i,frame:n,wire:s,bytes:r},counted:this.activeSend.counted}}}forgetTerminal(e,t){this.prepareForgetTerminal(e,t)()}prepareForgetTerminal(e,t){const i=this.rings.get(t),n=new Set(this.pending.filter(e=>e.terminalId===t)),s=this.activeSend?.item.terminalId===t&&this.activeSend.counted?this.activeSend:void 0;if(i?.some(t=>t.frame.clientId!==e)||[...n].some(t=>t.clientId!==e)||s&&s.item.clientId!==e)throw new Error("terminal forget ownership mismatch");const r=this.replayExpiryTimers.get(t);let a=!1;return()=>{if(a)return;a=!0,r&&this.replayExpiryTimers.get(t)===r&&(clearTimeout(r),this.replayExpiryTimers.delete(t)),i&&this.rings.get(t)===i&&(this.rings.delete(t),this.ringBytes.delete(t),this.droppedThrough.delete(t));let e=0;for(const t of this.pending)n.has(t)?this.pendingBytes-=t.bytes:this.pending[e++]=t;this.pending.length=e,s&&this.activeSend===s&&s.counted&&(s.counted=!1,this.pendingBytes-=s.item.bytes),this.pendingBytes=Math.max(0,this.pendingBytes)}}startTerminalReplayExpiry(e,t){if((this.rings.get(t)??[]).some(t=>t.frame.clientId!==e))throw new Error("terminal replay expiry ownership mismatch");this.scheduleReplayExpiry(e,t)}drain(e){return this.drainPromise??=this.drainOnce(e),this.drainPromise}async drainOnce(e){this.draining=!0,this.stopped=!0,this.eventGeneration++;const t=[...this.flushes];this.invalidateConnection(!0);const i=await waitBounded(this.options.manager.drain(e.timeoutMs,e.terminateAfterTimeout),e.timeoutMs,e.signal),n=i===interrupted?"terminated":i;return i!==interrupted&&"terminated"===n&&e.terminateAfterTimeout&&await waitBounded(this.options.manager.drain(Math.min(1e3,Math.max(50,e.timeoutMs)),!1),Math.min(1e3,Math.max(50,e.timeoutMs)),e.signal),await waitBounded(Promise.allSettled(t),e.timeoutMs,e.signal),await waitBounded(this.closeClientOnce(),e.timeoutMs,e.signal),this.clearReplayState(),n}acceptsEvent(e){return!this.stopped&&e===this.eventGeneration}async handleWire(e,t,i){if(!this.acceptsTransport(i,t))return;if(Buffer.byteLength(e)>TERMINAL_MAX_FRAME_BYTES)return void this.options.logger?.warn?.("[terminal-service] oversized client frame rejected");let n,s;try{n=JSON.parse(e)}catch{return void this.options.logger?.warn?.("[terminal-service] invalid client JSON")}try{s=await this.authorityAdmission.admit(n),await this.service.handleClientFrame(s.frame,t,s.admission)}catch(e){if(this.acceptsTransport(i,t)){s&&this.sendRejectedFrame(s.frame,e);const t=e instanceof Error?e.message:String(e);this.options.logger?.warn?.(`[terminal-service] client frame rejected: ${t}`,e)}}}sendRejectedFrame(e,t){const i=(t instanceof Error?t.message:String(t)).toLowerCase(),n=t instanceof TerminalNotFoundError||i.includes("not found")?"TERMINAL_NOT_FOUND":i.includes("quota")?"TERMINAL_QUOTA_EXCEEDED":i.includes("unauthorized")||i.includes("authority")?"UNAUTHORIZED_TARGET":"TERMINAL_IO_ERROR",s=this.rings.get(e.terminalId),r="TERMINAL_NOT_FOUND"!==n&&this.options.manager.getSession(e.terminalId)?.sessionId===e.clientId&&(!s||s.every(t=>t.frame.clientId===e.clientId)),a=r?this.options.manager.getSessionIncarnation(e.terminalId):void 0,o=r||"terminal.resume"!==e.type?a?BigInt(this.options.manager.nextServerSequence(e.terminalId,a)):1n:BigInt(e.lastServerSequence)+1n,c={protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.error",clientId:e.clientId,terminalId:e.terminalId,sequence:String(o),code:n,message:n,recoverable:"TERMINAL_IO_ERROR"===n||"TERMINAL_QUOTA_EXCEEDED"===n};if(!r){const t=JSON.stringify(c);return this.enqueueWithOutputBackpressure({clientId:e.clientId,terminalId:e.terminalId,frame:c,wire:t,bytes:Buffer.byteLength(t)}),void this.kickFlush()}this.send(c)}remember(e,t,i){const n=this.rings.get(e.terminalId)??[];n.push({frame:Object.freeze({...e}),wire:t,bytes:i});let s=(this.ringBytes.get(e.terminalId)??0)+i;const r=this.options.maxBufferedFramesPerTerminal??512,a=this.options.maxBufferedBytesPerTerminal??1048576;for(;n.length>r||s>a;){const t=n.shift();if(!t)break;s-=t.bytes,this.droppedThrough.set(e.terminalId,BigInt(t.frame.sequence))}this.rings.set(e.terminalId,n),this.ringBytes.set(e.terminalId,s),"terminal.exit"===e.type&&this.scheduleReplayExpiry(e.clientId,e.terminalId)}enqueueWithOutputBackpressure(e){const t=this.options.maxPendingSendBytes??1048576;if(this.pendingBytes+e.bytes<=t)return this.pending.push(e),void(this.pendingBytes+=e.bytes);if("terminal.output"===e.frame.type){if(this.hasPendingTruncation(e.terminalId))return;const i=this.toTruncationMarker(e);return this.compactPendingOutput(i.bytes,t),void(this.hasPendingTruncation(e.terminalId)||(this.pending.push(i),this.pendingBytes+=i.bytes))}this.compactPendingOutput(e.bytes,t),this.pending.push(e),this.pendingBytes+=e.bytes}compactPendingOutput(e,t){for(;this.pendingBytes+e>t;){const e=this.pending.findIndex(e=>"terminal.output"===e.frame.type&&!0!==e.frame.truncated);if(e<0)return;const t=this.pending[e];if(this.hasPendingTruncation(t.terminalId,t)){this.pending.splice(e,1),this.pendingBytes-=t.bytes;continue}const i=this.toTruncationMarker(t);this.pending[e]=i,this.pendingBytes+=i.bytes-t.bytes}}hasPendingTruncation(e,t){return this.activeSend?.item!==t&&this.activeSend?.item.terminalId===e&&"terminal.output"===this.activeSend.item.frame.type&&!0===this.activeSend.item.frame.truncated||this.pending.some(i=>i!==t&&i.terminalId===e&&"terminal.output"===i.frame.type&&!0===i.frame.truncated)}toTruncationMarker(e){if("terminal.output"!==e.frame.type)throw new Error("only terminal output can be truncated");const t={...e.frame,data:"",encoding:"base64",truncated:!0},i=JSON.stringify(t);return{...e,frame:t,wire:i,bytes:Buffer.byteLength(i)}}async flush(e){for(;this.acceptsConnection(e)&&this.pending.length>0;){const t=this.pending.shift(),i={item:t,counted:!0};this.activeSend=i;try{await this.options.client.send(t.wire)}catch{return void(this.transportEpoch===e&&this.invalidateConnection(!1))}if(!this.acceptsConnection(e))return;this.activeSend===i&&(i.counted&&(this.pendingBytes-=t.bytes),this.activeSend=void 0)}}kickFlush(){if(this.activeFlush||!this.connected||0===this.pending.length)return;const e=this.transportEpoch,t=this.flush(e).finally(()=>{this.flushes.delete(t),this.activeFlush?.promise===t&&(this.activeFlush=void 0,this.acceptsConnection(e)&&this.kickFlush())});this.activeFlush={epoch:e,promise:t},this.flushes.add(t)}acceptsConnection(e){return this.connected&&!this.stopped&&e===this.transportEpoch}acceptsTransport(e,t){return this.acceptsEvent(this.eventGeneration)&&this.acceptsConnection(e)&&this.serviceGeneration===t}invalidateConnection(e){const t=this.connected;if(this.transportEpoch++,this.connected=!1,t&&this.publishAvailability(!1),this.activeFlush=void 0,this.activeSend=void 0,this.flushes.clear(),this.pending.length=0,this.pendingBytes=0,!e)return;const i=this.serviceGeneration;i&&(this.service.fenceServiceSessions("runtime_lease_lost",i),this.service.clearOutbound(this,i),this.serviceGeneration===i&&(this.serviceGeneration=void 0)),this.clearReplayState()}publishAvailability(e){try{this.options.onAvailabilityChange?.(e)}catch{}}clearReplayState(){for(const e of this.replayExpiryTimers.values())clearTimeout(e);this.replayExpiryTimers.clear(),this.rings.clear(),this.ringBytes.clear(),this.droppedThrough.clear()}acknowledgeReplay(e,t){const i=this.rings.get(e);if(!i||0===i.length)return;let n=0,s=this.droppedThrough.get(e)??0n,r=0;for(;r<i.length;){const e=i[r],a=BigInt(e.frame.sequence);if(a>t)break;n+=e.bytes,a>s&&(s=a),r++}0!==r&&(i.splice(0,r),this.ringBytes.set(e,Math.max(0,(this.ringBytes.get(e)??0)-n)),this.droppedThrough.set(e,s))}scheduleReplayExpiry(e,t){this.clearReplayExpiry(t);const i=this.serviceGeneration;if(!i)return;const n=setTimeout(()=>{if(this.replayExpiryTimers.get(t)===n){this.replayExpiryTimers.delete(t);try{this.service.expireServiceTerminalReplay(e,t,i)}catch(e){this.options.logger?.warn?.("[terminal-service] replay expiry failed",e)}}},Math.max(0,this.options.replayRetentionMs??3e5));n.unref?.(),this.replayExpiryTimers.set(t,n)}clearReplayExpiry(e){const t=this.replayExpiryTimers.get(e);t&&clearTimeout(t),this.replayExpiryTimers.delete(e)}closeClientOnce(){return this.closePromise??=this.options.client.close(!0),this.closePromise}}const interrupted=Symbol("interrupted");async function waitBounded(e,t,i){return i?.aborted?(e.catch(()=>{}),interrupted):await new Promise((n,s)=>{let r=!1;const a=e=>{r||(r=!0,clearTimeout(c),i?.removeEventListener("abort",o),n(e))},o=()=>a(interrupted),c=setTimeout(()=>a(interrupted),Math.max(0,t));c.unref?.(),i?.addEventListener("abort",o,{once:!0}),e.then(e=>a(e),e=>{r||(r=!0,clearTimeout(c),i?.removeEventListener("abort",o),s(e))})})}
1
+ import{TERMINAL_MAX_FRAME_BYTES,TERMINAL_SERVICE_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/runtime";import{TerminalNotFoundError}from"./session-manager.js";import{getOrCreateRuntimeTerminalService}from"./runtime-terminal-service.js";import{TerminalAuthorityAdmission}from"./terminal-authority-admission.js";const DEFAULT_REPLAY_RETENTION_MS=3e5,DEFAULT_LEASE_RECOVERY_GRACE_MS=3e5;export class RuntimeTerminalOutboundRegistration{options;service;authorityAdmission;serviceGeneration;rings=new Map;ringBytes=new Map;droppedThrough=new Map;pending=[];pendingBytes=0;activeSend;activeFlush;flushes=new Set;connected=!1;handlerIdentity;draining=!1;stopped=!1;eventGeneration=0;transportEpoch=0;drainPromise;closePromise;replayExpiryTimers=new Map;constructor(e){this.options=e,this.service=e.service??getOrCreateRuntimeTerminalService(e.manager),this.authorityAdmission=new TerminalAuthorityAdmission(e.authorityJournal,e.authorityClock),this.serviceGeneration=this.service.setOutbound(this);const t=this.eventGeneration;e.client.on("connected",(...i)=>{if(!this.acceptsEvent(t))return;if(this.connected)return;this.transportEpoch++,this.activeFlush=void 0,this.serviceGeneration??=this.service.setOutbound(this);const n=i[0]?.handlerIdentity;n&&(this.handlerIdentity=n,this.service.closeParkedServiceSessions("runtime_lease_lost",n)),this.connected=!0,this.publishAvailability(!0),this.kickFlush(),e.logger?.info?.("[terminal-service] outbound registration connected")}),e.client.on("disconnected",()=>{this.acceptsEvent(t)&&this.invalidateConnection("transport")}),e.client.on("message",(...e)=>{if(!this.acceptsEvent(t))return;const i=this.serviceGeneration,n=this.transportEpoch;if(!i||!this.acceptsTransport(n,i))return;const r=e[0],s="string"==typeof r?r:Buffer.from(r).toString("utf8");this.handleWire(s,i,n)}),e.client.on("error",(...i)=>{this.acceptsEvent(t)&&e.logger?.warn?.("[terminal-service] outbound registration error",i[0])}),e.client.on("authorityLost",(...i)=>{if(this.acceptsEvent(t)){if(!0===i[0]?.recoverable)return this.invalidateConnection("park"),void e.logger?.warn?.("[terminal-service] runtime lease lost; Service terminals parked for recovery");this.invalidateConnection("fence"),e.logger?.warn?.("[terminal-service] runtime lease lost; Service terminals fenced")}}),e.client.on("transportEpochChanged",()=>{this.acceptsEvent(t)&&(this.invalidateConnection("park"),e.logger?.info?.("[terminal-service] runtime lease rotated; prior Terminal epoch parked for resume"))})}async start(){if(this.stopped)throw new Error("terminal outbound registration is stopped");await this.options.client.connect()}isAvailable(){return this.connected&&!this.draining&&!this.stopped}send(e){if(this.stopped)return!1;const t=JSON.stringify(e),i=Buffer.byteLength(t);return this.remember(e,t,i),!this.connected||(this.enqueueWithOutputBackpressure({clientId:e.clientId,terminalId:e.terminalId,frame:e,wire:t,bytes:i}),this.kickFlush(),!0)}resume(e,t,i){const n=this.transportEpoch;if(!this.acceptsConnection(n))throw new Error("terminal outbound registration is disconnected");const r=BigInt(i),s=r<(this.droppedThrough.get(t)??0n),a=[];let o=-1n;for(const i of this.rings.get(t)??[]){if(i.frame.clientId!==e)throw new Error("terminal resume ownership mismatch");const n=BigInt(i.frame.sequence);if(n<=o)throw new Error("terminal replay sequence is not monotonic");o=n,n<=r||a.push({clientId:e,terminalId:t,frame:i.frame,wire:i.wire,bytes:i.bytes})}if(!this.acceptsConnection(n))throw new Error("terminal outbound registration epoch changed");for(const e of a)this.enqueueWithOutputBackpressure(e);return this.acknowledgeReplay(t,r),this.kickFlush(),{gap:s}}rebindResumeClient(e,t,i){if(e===t)return;const n=this.rings.get(i);if(n?.some(t=>t.frame.clientId!==e))throw new Error("terminal resume ownership mismatch");if(n){let e=0;const r=n.map(i=>{const n=Object.freeze({...i.frame,clientId:t}),r=JSON.stringify(n),s=Buffer.byteLength(r);return e+=s,{frame:n,wire:r,bytes:s}});this.rings.set(i,r),this.ringBytes.set(i,e)}for(let n=0;n<this.pending.length;n++){const r=this.pending[n];if(r.terminalId!==i||r.clientId!==e)continue;const s={...r.frame,clientId:t},a=JSON.stringify(s),o=Buffer.byteLength(a);this.pendingBytes+=o-r.bytes,this.pending[n]={clientId:t,terminalId:i,frame:s,wire:a,bytes:o}}if(this.activeSend?.item.terminalId===i&&this.activeSend.item.clientId===e){const e=this.activeSend.item,n={...e.frame,clientId:t},r=JSON.stringify(n),s=Buffer.byteLength(r);this.activeSend.counted&&(this.pendingBytes+=s-e.bytes),this.activeSend={item:{clientId:t,terminalId:i,frame:n,wire:r,bytes:s},counted:this.activeSend.counted}}}forgetTerminal(e,t){this.prepareForgetTerminal(e,t)()}prepareForgetTerminal(e,t){const i=this.rings.get(t),n=new Set(this.pending.filter(e=>e.terminalId===t)),r=this.activeSend?.item.terminalId===t&&this.activeSend.counted?this.activeSend:void 0;if(i?.some(t=>t.frame.clientId!==e)||[...n].some(t=>t.clientId!==e)||r&&r.item.clientId!==e)throw new Error("terminal forget ownership mismatch");const s=this.replayExpiryTimers.get(t);let a=!1;return()=>{if(a)return;a=!0,s&&this.replayExpiryTimers.get(t)===s&&(clearTimeout(s),this.replayExpiryTimers.delete(t)),i&&this.rings.get(t)===i&&(this.rings.delete(t),this.ringBytes.delete(t),this.droppedThrough.delete(t));let e=0;for(const t of this.pending)n.has(t)?this.pendingBytes-=t.bytes:this.pending[e++]=t;this.pending.length=e,r&&this.activeSend===r&&r.counted&&(r.counted=!1,this.pendingBytes-=r.item.bytes),this.pendingBytes=Math.max(0,this.pendingBytes)}}startTerminalReplayExpiry(e,t){if((this.rings.get(t)??[]).some(t=>t.frame.clientId!==e))throw new Error("terminal replay expiry ownership mismatch");this.scheduleReplayExpiry(e,t)}drain(e){return this.drainPromise??=this.drainOnce(e),this.drainPromise}async drainOnce(e){this.draining=!0,this.stopped=!0,this.eventGeneration++;const t=[...this.flushes];this.invalidateConnection("fence");const i=await waitBounded(this.options.manager.drain(e.timeoutMs,e.terminateAfterTimeout),e.timeoutMs,e.signal),n=i===interrupted?"terminated":i;return i!==interrupted&&"terminated"===n&&e.terminateAfterTimeout&&await waitBounded(this.options.manager.drain(Math.min(1e3,Math.max(50,e.timeoutMs)),!1),Math.min(1e3,Math.max(50,e.timeoutMs)),e.signal),await waitBounded(Promise.allSettled(t),e.timeoutMs,e.signal),await waitBounded(this.closeClientOnce(),e.timeoutMs,e.signal),this.clearReplayState(),n}acceptsEvent(e){return!this.stopped&&e===this.eventGeneration}async handleWire(e,t,i){if(!this.acceptsTransport(i,t))return;if(Buffer.byteLength(e)>TERMINAL_MAX_FRAME_BYTES)return void this.options.logger?.warn?.("[terminal-service] oversized client frame rejected");let n,r;try{n=JSON.parse(e)}catch{return void this.options.logger?.warn?.("[terminal-service] invalid client JSON")}try{r=await this.authorityAdmission.admit(n),await this.service.handleClientFrame(r.frame,t,r.admission)}catch(e){if(this.acceptsTransport(i,t)){r&&this.sendRejectedFrame(r.frame,e);const t=e instanceof Error?e.message:String(e);this.options.logger?.warn?.(`[terminal-service] client frame rejected: ${t}`,e)}}}sendRejectedFrame(e,t){const i=(t instanceof Error?t.message:String(t)).toLowerCase(),n=t instanceof TerminalNotFoundError||i.includes("not found")?"TERMINAL_NOT_FOUND":i.includes("quota")?"TERMINAL_QUOTA_EXCEEDED":i.includes("unauthorized")||i.includes("authority")?"UNAUTHORIZED_TARGET":"TERMINAL_IO_ERROR",r=this.rings.get(e.terminalId),s="TERMINAL_NOT_FOUND"!==n&&this.options.manager.getSession(e.terminalId)?.sessionId===e.clientId&&(!r||r.every(t=>t.frame.clientId===e.clientId)),a=s?this.options.manager.getSessionIncarnation(e.terminalId):void 0,o=s||"terminal.resume"!==e.type?a?BigInt(this.options.manager.nextServerSequence(e.terminalId,a)):1n:BigInt(e.lastServerSequence)+1n,c={protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.error",clientId:e.clientId,terminalId:e.terminalId,sequence:String(o),code:n,message:n,recoverable:"TERMINAL_IO_ERROR"===n||"TERMINAL_QUOTA_EXCEEDED"===n};if(!s){const t=JSON.stringify(c);return this.enqueueWithOutputBackpressure({clientId:e.clientId,terminalId:e.terminalId,frame:c,wire:t,bytes:Buffer.byteLength(t)}),void this.kickFlush()}this.send(c)}remember(e,t,i){const n=this.rings.get(e.terminalId)??[];n.push({frame:Object.freeze({...e}),wire:t,bytes:i});let r=(this.ringBytes.get(e.terminalId)??0)+i;const s=this.options.maxBufferedFramesPerTerminal??512,a=this.options.maxBufferedBytesPerTerminal??1048576;for(;n.length>s||r>a;){const t=n.shift();if(!t)break;r-=t.bytes,this.droppedThrough.set(e.terminalId,BigInt(t.frame.sequence))}this.rings.set(e.terminalId,n),this.ringBytes.set(e.terminalId,r),"terminal.exit"===e.type&&this.scheduleReplayExpiry(e.clientId,e.terminalId)}enqueueWithOutputBackpressure(e){const t=this.options.maxPendingSendBytes??1048576;if(this.pendingBytes+e.bytes<=t)return this.pending.push(e),void(this.pendingBytes+=e.bytes);if("terminal.output"===e.frame.type){if(this.hasPendingTruncation(e.terminalId))return;const i=this.toTruncationMarker(e);return this.compactPendingOutput(i.bytes,t),void(this.hasPendingTruncation(e.terminalId)||(this.pending.push(i),this.pendingBytes+=i.bytes))}this.compactPendingOutput(e.bytes,t),this.pending.push(e),this.pendingBytes+=e.bytes}compactPendingOutput(e,t){for(;this.pendingBytes+e>t;){const e=this.pending.findIndex(e=>"terminal.output"===e.frame.type&&!0!==e.frame.truncated);if(e<0)return;const t=this.pending[e];if(this.hasPendingTruncation(t.terminalId,t)){this.pending.splice(e,1),this.pendingBytes-=t.bytes;continue}const i=this.toTruncationMarker(t);this.pending[e]=i,this.pendingBytes+=i.bytes-t.bytes}}hasPendingTruncation(e,t){return this.activeSend?.item!==t&&this.activeSend?.item.terminalId===e&&"terminal.output"===this.activeSend.item.frame.type&&!0===this.activeSend.item.frame.truncated||this.pending.some(i=>i!==t&&i.terminalId===e&&"terminal.output"===i.frame.type&&!0===i.frame.truncated)}toTruncationMarker(e){if("terminal.output"!==e.frame.type)throw new Error("only terminal output can be truncated");const t={...e.frame,data:"",encoding:"base64",truncated:!0},i=JSON.stringify(t);return{...e,frame:t,wire:i,bytes:Buffer.byteLength(i)}}async flush(e){for(;this.acceptsConnection(e)&&this.pending.length>0;){const t=this.pending.shift(),i={item:t,counted:!0};this.activeSend=i;try{await this.options.client.send(t.wire)}catch{return void(this.transportEpoch===e&&this.invalidateConnection("transport"))}if(!this.acceptsConnection(e))return;this.activeSend===i&&(i.counted&&(this.pendingBytes-=t.bytes),this.activeSend=void 0)}}kickFlush(){if(this.activeFlush||!this.connected||0===this.pending.length)return;const e=this.transportEpoch,t=this.flush(e).finally(()=>{this.flushes.delete(t),this.activeFlush?.promise===t&&(this.activeFlush=void 0,this.acceptsConnection(e)&&this.kickFlush())});this.activeFlush={epoch:e,promise:t},this.flushes.add(t)}acceptsConnection(e){return this.connected&&!this.stopped&&e===this.transportEpoch}acceptsTransport(e,t){return this.acceptsEvent(this.eventGeneration)&&this.acceptsConnection(e)&&this.serviceGeneration===t}invalidateConnection(e){const t=this.connected;this.transportEpoch++,this.connected=!1,t&&this.publishAvailability(!1),this.activeFlush=void 0,this.activeSend=void 0,this.flushes.clear(),this.pending.length=0,this.pendingBytes=0;const i=this.serviceGeneration;switch(e){case"transport":return;case"park":i&&this.service.parkServiceSessions(i,this.handlerIdentity,this.options.leaseRecoveryGraceMs??3e5);break;case"fence":this.service.closeParkedServiceSessions("runtime_lease_lost"),i&&this.service.fenceServiceSessions("runtime_lease_lost",i);break;default:throw new Error(`unhandled connection invalidation ${String(e)}`)}i&&(this.service.clearOutbound(this,i),this.serviceGeneration===i&&(this.serviceGeneration=void 0)),this.clearReplayState()}publishAvailability(e){try{this.options.onAvailabilityChange?.(e)}catch{}}clearReplayState(){for(const e of this.replayExpiryTimers.values())clearTimeout(e);this.replayExpiryTimers.clear(),this.rings.clear(),this.ringBytes.clear(),this.droppedThrough.clear()}acknowledgeReplay(e,t){const i=this.rings.get(e);if(!i||0===i.length)return;let n=0,r=this.droppedThrough.get(e)??0n,s=0;for(;s<i.length;){const e=i[s],a=BigInt(e.frame.sequence);if(a>t)break;n+=e.bytes,a>r&&(r=a),s++}0!==s&&(i.splice(0,s),this.ringBytes.set(e,Math.max(0,(this.ringBytes.get(e)??0)-n)),this.droppedThrough.set(e,r))}scheduleReplayExpiry(e,t){this.clearReplayExpiry(t);const i=this.serviceGeneration;if(!i)return;const n=setTimeout(()=>{if(this.replayExpiryTimers.get(t)===n){this.replayExpiryTimers.delete(t);try{this.service.expireServiceTerminalReplay(e,t,i)}catch(e){this.options.logger?.warn?.("[terminal-service] replay expiry failed",e)}}},Math.max(0,this.options.replayRetentionMs??3e5));n.unref?.(),this.replayExpiryTimers.set(t,n)}clearReplayExpiry(e){const t=this.replayExpiryTimers.get(e);t&&clearTimeout(t),this.replayExpiryTimers.delete(e)}closeClientOnce(){return this.closePromise??=this.options.client.close(!0),this.closePromise}}const interrupted=Symbol("interrupted");async function waitBounded(e,t,i){return i?.aborted?(e.catch(()=>{}),interrupted):await new Promise((n,r)=>{let s=!1;const a=e=>{s||(s=!0,clearTimeout(c),i?.removeEventListener("abort",o),n(e))},o=()=>a(interrupted),c=setTimeout(()=>a(interrupted),Math.max(0,t));c.unref?.(),i?.addEventListener("abort",o,{once:!0}),e.then(e=>a(e),e=>{s||(s=!0,clearTimeout(c),i?.removeEventListener("abort",o),r(e))})})}
@@ -1 +1 @@
1
- import{TERMINAL_SERVICE_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/runtime";import{TerminalNotFoundError}from"./session-manager.js";const MAX_OUTPUT_FRAME_BYTES=16384,OUTPUT_FLUSH_INTERVAL_MS=16;export class RuntimeTerminalService{manager;clients=new Map;clientSequences=new Map;openingFrames=new Map;pendingOutput=new Map;outboundGeneration;constructor(e,n){this.manager=e,n&&this.setOutbound(n),e.addListeners({onOutput:e=>{const n=this.clients.get(e.terminalId);"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.manager.isCurrentSession(e.terminalId,n.incarnation)&&this.queueOutput(e.terminalId,e.stream,e.data,e.seq,n)},onExit:e=>{const n=this.clients.get(e.terminalId);if("acp"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation){const t=setTimeout(()=>{this.clients.get(e.terminalId)===n&&(this.clients.delete(e.terminalId),this.manager.releaseSessionIfCurrent(e.terminalId,n.incarnation))},0);return void t.unref?.()}"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.manager.isCurrentSession(e.terminalId,n.incarnation)&&(this.flushPendingOutput(e.terminalId),this.emitFrame({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.exit",clientId:n.clientId,terminalId:e.terminalId,sequence:String(e.seq),...null===e.code?{}:{exitCode:e.code},...null===e.signal?{}:{signal:e.signal}},n))},onStateChange:e=>{if("closing"!==e.to)return;const n=this.clients.get(e.terminalId);if("acp"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation){const t=setTimeout(()=>{this.clients.get(e.terminalId)!==n||this.manager.isCurrentSession(e.terminalId,e.incarnation)||this.clients.delete(e.terminalId)},0);return void t.unref?.()}"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.isOwnedTerminal(n)&&n.generation.outbound.startTerminalReplayExpiry(n.clientId,e.terminalId)}})}setOutbound(e){if(this.outboundGeneration?.outbound===e)return this.outboundGeneration;if(this.outboundGeneration)throw new Error("Terminal outbound sink is already configured");const n=Object.freeze({outbound:e});return this.outboundGeneration=n,n}clearOutbound(e,n){const t=n??this.outboundGeneration;t&&t.outbound===e&&this.outboundGeneration===t&&(this.outboundGeneration=void 0,this.clearGenerationOwnership(t))}fenceServiceSessions(e="runtime_lease_lost",n=this.outboundGeneration){for(const[t,i]of this.clients)"service"!==i.kind||n&&i.generation!==n||this.manager.closeSessionIfCurrent(t,i.incarnation,e);if(n)this.clearGenerationOwnership(n);else{for(const[e,n]of this.clients)"service"===n.kind&&this.clients.delete(e);this.clientSequences.clear();for(const[e,n]of this.openingFrames)"service"===n.owner.kind&&this.openingFrames.delete(e)}}async handleClientFrame(e,n=this.outboundGeneration,t){if(!n||!this.isGenerationActive(n))throw new Error("terminal outbound generation is inactive");assertTerminalClientFrame(e);const i=BigInt(e.clientSequence),r=this.clientSequences.get(e.clientId);if(i<=(r?.generation===n?r.sequence:0n))throw new Error("non-monotonic terminal client sequence");if(this.clientSequences.set(e.clientId,{generation:n,sequence:i}),"terminal.open"===e.type||"terminal.create"===e.type){if(t?.recovered&&!this.clients.has(e.terminalId)&&!this.manager.getSession(e.terminalId))throw new TerminalNotFoundError(e.terminalId);if(this.clients.has(e.terminalId)||this.manager.getSession(e.terminalId)){const i=this.clients.get(e.terminalId);if(t?.recovered&&"service"===i?.kind&&i.clientId===e.clientId&&i.generation===n&&this.isOwnedTerminal(i)){const n=this.manager.getSession(e.terminalId);return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(i),resumed:!0,columns:n.cols,rows:n.rows},i)}throw new Error("terminal already exists; use terminal.resume")}const i=this.manager.createSessionIncarnation(e.terminalId),r={kind:"service",clientId:e.clientId,generation:n,incarnation:i};this.clients.set(e.terminalId,r),this.openingFrames.set(e.terminalId,{owner:r,frames:[]});try{const n=this.manager.openSession({terminalId:e.terminalId,sessionId:e.clientId,command:e.command,args:e.args,env:e.env,cwd:e.cwd,cols:"terminal.open"===e.type?e.columns??80:80,rows:"terminal.open"===e.type?e.rows??24:24,outputLimitBytes:e.outputLimitBytes,initialServerSequence:2,incarnation:i});if(!this.isOwnedTerminal(r))return void this.manager.releaseSessionIfCurrent(e.terminalId,i);this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:n.terminalId,sequence:"1",resumed:!0===t?.recovered,columns:n.cols,rows:n.rows},r);const s=this.openingFrames.get(e.terminalId);s?.owner===r&&this.openingFrames.delete(e.terminalId);for(const e of s?.owner===r?s.frames:[])this.sendOrClose(e,r)}catch(n){throw this.clients.get(e.terminalId)===r&&this.clients.delete(e.terminalId),this.openingFrames.get(e.terminalId)?.owner===r&&this.openingFrames.delete(e.terminalId),this.manager.releaseSessionIfCurrent(e.terminalId,i),n}return}"terminal.resume"===e.type&&this.rebindServiceOwnerForResume(e.clientId,e.terminalId,n);const s=this.assertOwner(e.clientId,e.terminalId,n);switch(e.type){case"terminal.resume":{const t=n.outbound.resume(e.clientId,e.terminalId,e.lastServerSequence);if(!this.isOwnedTerminal(s))return;if(t.gap&&this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.output",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(s),stream:"stdout",data:"",encoding:"base64",truncated:!0},s),!this.isOwnedTerminal(s))return;const i=this.manager.getSession(e.terminalId);return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(s),resumed:!0,columns:i.cols,rows:i.rows},s)}case"terminal.input":return void this.manager.writeToSessionIfCurrent(e.terminalId,s.incarnation,Buffer.from(e.data,"base64"===e.encoding?"base64":"utf8"));case"terminal.resize":return void this.manager.resizeSessionIfCurrent(e.terminalId,s.incarnation,e.columns,e.rows);case"terminal.close":if(!this.manager.closeSessionIfCurrent(e.terminalId,s.incarnation,e.reason))throw new TerminalNotFoundError(e.terminalId);return;case"terminal.ping":if(this.manager.touchSessionIfCurrent(e.terminalId,s.incarnation),!this.isOwnedTerminal(s))return;return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.pong",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(s),nonce:e.nonce},s);case"terminal.wait":{let n;try{n=await this.manager.waitForExitIfCurrent(e.terminalId,s.incarnation,e.timeoutMs)}catch(e){if(!this.isOwnedTerminal(s))return;throw e}if(!this.isOwnedTerminal(s))return;return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.wait_result",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(s),exitCode:n.exitCode,output:n.output,state:n.state},s)}case"terminal.release":if(!this.manager.isCurrentSession(e.terminalId,s.incarnation))throw new TerminalNotFoundError(e.terminalId);const t=n.outbound.prepareForgetTerminal(e.clientId,e.terminalId);if(!this.manager.detachSessionIfCurrent(e.terminalId,s.incarnation))throw new TerminalNotFoundError(e.terminalId);return this.clients.get(e.terminalId)===s&&this.clients.delete(e.terminalId),this.dropPendingOutput(e.terminalId),void t();case"terminal.kill":if(!this.manager.killSessionIfCurrent(e.terminalId,s.incarnation,normalizeSignal(e.signal)))throw new TerminalNotFoundError(e.terminalId);return}}async execute(e,n){const t=this.outboundGeneration,i=t?.outbound;if(i?.isAvailable&&!i.isAvailable())throw new Error("terminal Service transport is unavailable; legacy fallback is forbidden");const r=e,s=r.context.terminal.terminalId,a=r.context.terminal.clientId;switch(r.name){case"terminal.open":{const e=r,n=this.manager.createSessionIncarnation(s),t=this.manager.openSession({terminalId:s,sessionId:e.context.terminal.clientId,command:e.params.command,args:e.params.args,env:e.params.env,cwd:e.params.cwd,cols:e.params.columns,rows:e.params.rows,outputLimitBytes:e.params.outputLimitBytes,incarnation:n});return this.clients.set(t.terminalId,{kind:"acp",clientId:e.context.terminal.clientId,incarnation:n}),{terminalId:t.terminalId,command:e.params.command??t.shell,shell:t.shell,cwd:t.cwd,columns:t.cols,rows:t.rows,state:t.state}}case"terminal.input":{const e=r,n=this.assertAcpOwner(a,s);return this.manager.writeToSessionIfCurrent(s,n.incarnation,Buffer.from(e.params.data,"base64"===e.params.encoding?"base64":"utf8")),{accepted:!0}}case"terminal.resize":{const e=r,n=this.assertAcpOwner(a,s);return this.manager.resizeSessionIfCurrent(s,n.incarnation,e.params.columns,e.params.rows),{resized:!0}}case"terminal.close":{const e=this.assertAcpOwner(a,s);if(!this.manager.closeSessionIfCurrent(s,e.incarnation,r.params.reason))throw new TerminalNotFoundError(s);return{closed:!0}}case"terminal.ping":{const e=this.assertAcpOwner(a,s);return this.manager.touchSessionIfCurrent(s,e.incarnation),{terminalId:s,status:"ok"}}case"terminal.create":{const e=r,n=this.manager.createSessionIncarnation(s),t=this.manager.openSession({terminalId:s,sessionId:e.context.terminal.clientId,command:e.params.command,args:e.params.args,env:e.params.env,cwd:e.params.cwd,cols:80,rows:24,outputLimitBytes:e.params.outputLimitBytes,incarnation:n});return this.clients.set(t.terminalId,{kind:"acp",clientId:e.context.terminal.clientId,incarnation:n}),{terminalId:t.terminalId,command:e.params.command}}case"terminal.wait":{const e=r,n=this.assertAcpOwner(a,s),t=await this.manager.waitForExitIfCurrent(s,n.incarnation,e.params.timeoutMs);if(!this.isOwnedAcpTerminal(n))throw new TerminalNotFoundError(s);return t}case"terminal.release":{const e=this.assertAcpOwner(a,s);if(!this.manager.detachSessionIfCurrent(s,e.incarnation))throw new TerminalNotFoundError(s);return this.clients.get(s)===e&&this.clients.delete(s),{released:!0}}case"terminal.kill":{const e=this.assertAcpOwner(a,s);if(!this.manager.killSessionIfCurrent(s,e.incarnation,normalizeSignal(r.params.signal)))throw new TerminalNotFoundError(s);return{killed:!0}}}throw new Error(`unsupported terminal command ${r.name}`)}nextSequence(e){return String(this.manager.nextServerSequence(e.incarnation.terminalId,e.incarnation))}sendOrClose(e,n){this.isOwnedTerminal(n)&&!n.generation.outbound.send(e)&&"terminal.output"!==e.type&&this.isOwnedTerminal(n)&&this.manager.closeSessionIfCurrent(e.terminalId,n.incarnation,"backpressure_limit")}emitFrame(e,n){if(!this.isOwnedTerminal(n))return;const t=this.openingFrames.get(e.terminalId);t?.owner===n?t.frames.push(e):this.sendOrClose(e,n)}assertOwner(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.clientId!==e||i.generation!==t||!this.isOwnedTerminal(i))throw new TerminalNotFoundError(n);return i}rebindServiceOwnerForResume(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.generation!==t||!this.isOwnedTerminal(i))return;if(i.clientId===e)return;if(this.flushPendingOutput(n),!this.manager.rebindSessionId(n,i.incarnation,e))throw new TerminalNotFoundError(n);t.outbound.rebindResumeClient(i.clientId,e,n),this.clientSequences.delete(i.clientId);const r={kind:"service",clientId:e,generation:i.generation,incarnation:i.incarnation};this.clients.set(n,r)}assertAcpOwner(e,n){const t=this.clients.get(n);if("acp"!==t?.kind||t.clientId!==e||!this.isOwnedAcpTerminal(t))throw new TerminalNotFoundError(n);return t}isGenerationActive(e){return this.outboundGeneration===e}isOwnedTerminal(e){return this.outboundGeneration===e.generation&&this.clients.get(e.incarnation.terminalId)===e&&this.manager.isCurrentSession(e.incarnation.terminalId,e.incarnation)}isOwnedAcpTerminal(e){return this.clients.get(e.incarnation.terminalId)===e&&this.manager.isCurrentSession(e.incarnation.terminalId,e.incarnation)}expireServiceTerminalReplay(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.clientId!==e||i.generation!==t||!this.isOwnedTerminal(i))return;const r=t.outbound.prepareForgetTerminal(e,n);if(!("closed"===this.manager.getSession(n)?.state?this.manager.releaseSessionIfCurrent(n,i.incarnation):this.manager.detachSessionIfCurrent(n,i.incarnation)))throw new TerminalNotFoundError(n);this.clients.get(n)===i&&this.clients.delete(n),this.openingFrames.get(n)?.owner===i&&this.openingFrames.delete(n),this.dropPendingOutput(n),r()}clearGenerationOwnership(e){for(const[n,t]of this.clients)"service"===t.kind&&t.generation===e&&this.clients.delete(n);for(const[n,t]of this.clientSequences)t.generation===e&&this.clientSequences.delete(n);for(const[n,t]of this.openingFrames)t.owner.generation===e&&this.openingFrames.delete(n);for(const[n,t]of this.pendingOutput)t.owner.generation===e&&this.dropPendingOutput(n)}queueOutput(e,n,t,i,r){let s=0,a=i;for(;s<t.length;){let i=this.pendingOutput.get(e);!i||i.owner===r&&i.stream===n||(this.flushPendingOutput(e),i=void 0),i||(i={owner:r,stream:n,chunks:[],bytes:0,sequence:a},this.pendingOutput.set(e,i));const o=Math.min(16384-i.bytes,t.length-s);i.chunks.push(t.subarray(s,s+o)),i.bytes+=o,i.sequence=a,s+=o,16384!==i.bytes?i.timer||(i.timer=setTimeout(()=>{this.pendingOutput.get(e)===i&&this.flushPendingOutput(e)},16),i.timer.unref?.()):(this.flushPendingOutput(e),s<t.length&&(a=this.manager.nextServerSequence(e,r.incarnation)))}}flushPendingOutput(e){const n=this.pendingOutput.get(e);if(!n)return;if(this.pendingOutput.delete(e),n.timer&&clearTimeout(n.timer),0===n.bytes||!this.isOwnedTerminal(n.owner))return;const t=1===n.chunks.length?n.chunks[0]:Buffer.concat(n.chunks,n.bytes);this.emitFrame({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.output",clientId:n.owner.clientId,terminalId:e,sequence:String(n.sequence),stream:n.stream,data:t.toString("base64"),encoding:"base64"},n.owner)}dropPendingOutput(e){const n=this.pendingOutput.get(e);n&&(n.timer&&clearTimeout(n.timer),this.pendingOutput.delete(e))}}const runtimeServices=new WeakMap;export function getOrCreateRuntimeTerminalService(e){let n=runtimeServices.get(e);return n||(n=new RuntimeTerminalService(e),runtimeServices.set(e,n)),n}export function assertTerminalClientFrame(e){const n=e;if(!(n&&CLIENT_FRAME_TYPES.has(String(n.type))&&n.protocolVersion===TERMINAL_SERVICE_PROTOCOL_VERSION&&isUuid(n.clientId)&&isUuid(n.terminalId)&&isPositiveRuntimeSequence(n.clientSequence)))throw new Error("invalid terminal client frame");switch(e.type){case"terminal.open":assertOptionalCommand(e.command),assertOptionalArgs(e.args),assertOptionalEnv(e.env),assertOptionalString(e.cwd),assertOptionalDimension(e.columns),assertOptionalDimension(e.rows),assertOptionalOutputLimit(e.outputLimitBytes);break;case"terminal.create":("string"!=typeof e.command||e.command.length<1||e.command.length>4096)&&invalidFrame(),assertOptionalArgs(e.args),assertOptionalEnv(e.env),assertOptionalString(e.cwd),assertOptionalOutputLimit(e.outputLimitBytes);break;case"terminal.resume":isRuntimeSequence(e.lastServerSequence)||invalidFrame();break;case"terminal.input":("string"!=typeof e.data||"utf8"!==e.encoding&&"base64"!==e.encoding)&&invalidFrame();break;case"terminal.resize":assertDimension(e.columns),assertDimension(e.rows);break;case"terminal.close":assertOptionalString(e.reason);break;case"terminal.ping":"string"!=typeof e.nonce&&invalidFrame();break;case"terminal.wait":void 0!==e.timeoutMs&&(!Number.isInteger(e.timeoutMs)||e.timeoutMs<0||e.timeoutMs>864e5)&&invalidFrame();break;case"terminal.kill":assertOptionalString(e.signal)}}const CLIENT_FRAME_TYPES=new Set(["terminal.open","terminal.resume","terminal.input","terminal.resize","terminal.close","terminal.ping","terminal.create","terminal.wait","terminal.release","terminal.kill"]);function normalizeSignal(e){return e&&e.startsWith("SIG")?e:"SIGTERM"}const UUID_PATTERN=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,UINT64_MAX=18446744073709551615n;function isUuid(e){return"string"==typeof e&&UUID_PATTERN.test(e)}function isRuntimeSequence(e){return"string"==typeof e&&/^(0|[1-9][0-9]*)$/.test(e)&&BigInt(e)<=UINT64_MAX}function isPositiveRuntimeSequence(e){return isRuntimeSequence(e)&&"0"!==e}function invalidFrame(){throw new Error("invalid terminal client frame")}function assertOptionalString(e){void 0!==e&&"string"!=typeof e&&invalidFrame()}function assertOptionalCommand(e){void 0!==e&&("string"!=typeof e||e.length<1||e.length>4096)&&invalidFrame()}function assertOptionalArgs(e){void 0===e||Array.isArray(e)&&e.every(e=>"string"==typeof e)||invalidFrame()}function assertOptionalEnv(e){void 0===e||e&&"object"==typeof e&&!Array.isArray(e)&&Object.values(e).every(e=>"string"==typeof e)||invalidFrame()}function assertDimension(e){(!Number.isInteger(e)||e<1||e>1e3)&&invalidFrame()}function assertOptionalDimension(e){void 0!==e&&assertDimension(e)}function assertOptionalOutputLimit(e){void 0!==e&&(!Number.isInteger(e)||e<1||e>16777216)&&invalidFrame()}
1
+ import{TERMINAL_SERVICE_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/runtime";import{TerminalNotFoundError}from"./session-manager.js";const MAX_OUTPUT_FRAME_BYTES=16384,OUTPUT_FLUSH_INTERVAL_MS=16;export class RuntimeTerminalService{manager;clients=new Map;clientSequences=new Map;openingFrames=new Map;pendingOutput=new Map;parked=new Map;outboundGeneration;constructor(e,n){this.manager=e,n&&this.setOutbound(n),e.addListeners({onOutput:e=>{const n=this.clients.get(e.terminalId);"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.manager.isCurrentSession(e.terminalId,n.incarnation)&&this.queueOutput(e.terminalId,e.stream,e.data,e.seq,n)},onExit:e=>{const n=this.clients.get(e.terminalId);if("acp"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation){const t=setTimeout(()=>{this.clients.get(e.terminalId)===n&&(this.clients.delete(e.terminalId),this.manager.releaseSessionIfCurrent(e.terminalId,n.incarnation))},0);return void t.unref?.()}"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.manager.isCurrentSession(e.terminalId,n.incarnation)&&(this.flushPendingOutput(e.terminalId),this.emitFrame({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.exit",clientId:n.clientId,terminalId:e.terminalId,sequence:String(e.seq),...null===e.code?{}:{exitCode:e.code},...null===e.signal?{}:{signal:e.signal}},n))},onStateChange:e=>{if("closing"!==e.to)return;const n=this.clients.get(e.terminalId);if("acp"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation){const t=setTimeout(()=>{this.clients.get(e.terminalId)!==n||this.manager.isCurrentSession(e.terminalId,e.incarnation)||this.clients.delete(e.terminalId)},0);return void t.unref?.()}"service"===n?.kind&&n.clientId===e.sessionId&&n.incarnation===e.incarnation&&this.isOwnedTerminal(n)&&n.generation.outbound.startTerminalReplayExpiry(n.clientId,e.terminalId)}})}setOutbound(e){if(this.outboundGeneration?.outbound===e)return this.outboundGeneration;if(this.outboundGeneration)throw new Error("Terminal outbound sink is already configured");const n=Object.freeze({outbound:e});return this.outboundGeneration=n,n}clearOutbound(e,n){const t=n??this.outboundGeneration;t&&t.outbound===e&&this.outboundGeneration===t&&(this.outboundGeneration=void 0,this.clearGenerationOwnership(t))}fenceServiceSessions(e="runtime_lease_lost",n=this.outboundGeneration){for(const[t,i]of this.clients)"service"!==i.kind||n&&i.generation!==n||this.manager.closeSessionIfCurrent(t,i.incarnation,e);if(n)this.clearGenerationOwnership(n);else{for(const[e,n]of this.clients)"service"===n.kind&&this.clients.delete(e);this.clientSequences.clear();for(const[e,n]of this.openingFrames)"service"===n.owner.kind&&this.openingFrames.delete(e)}}parkServiceSessions(e,n,t){for(const[i,r]of this.clients){if("service"!==r.kind||r.generation!==e)continue;if("running"!==this.manager.getSession(i)?.state){this.manager.closeSessionIfCurrent(i,r.incarnation,"runtime_lease_lost");continue}const s=setTimeout(()=>{this.parked.get(i)?.timer===s&&this.closeParked(i,"runtime_lease_lost")},Math.max(0,t));s.unref?.(),this.parked.set(i,{clientId:r.clientId,incarnation:r.incarnation,handlerIdentity:n,timer:s})}this.clearGenerationOwnership(e)}closeParkedServiceSessions(e,n){for(const[t,i]of this.parked)void 0!==n&&i.handlerIdentity===n||this.closeParked(t,e)}async handleClientFrame(e,n=this.outboundGeneration,t){if(!n||!this.isGenerationActive(n))throw new Error("terminal outbound generation is inactive");assertTerminalClientFrame(e);const i=BigInt(e.clientSequence),r=this.clientSequences.get(e.clientId);if(i<=(r?.generation===n?r.sequence:0n))throw new Error("non-monotonic terminal client sequence");if(this.clientSequences.set(e.clientId,{generation:n,sequence:i}),"terminal.open"===e.type||"terminal.create"===e.type){if(t?.recovered&&!this.clients.has(e.terminalId)&&!this.manager.getSession(e.terminalId))throw new TerminalNotFoundError(e.terminalId);if(this.clients.has(e.terminalId)||this.manager.getSession(e.terminalId)){const i=this.clients.get(e.terminalId);if(t?.recovered&&"service"===i?.kind&&i.clientId===e.clientId&&i.generation===n&&this.isOwnedTerminal(i)){const n=this.manager.getSession(e.terminalId);return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(i),resumed:!0,columns:n.cols,rows:n.rows},i)}throw new Error("terminal already exists; use terminal.resume")}const i=this.manager.createSessionIncarnation(e.terminalId),r={kind:"service",clientId:e.clientId,generation:n,incarnation:i};this.clients.set(e.terminalId,r),this.openingFrames.set(e.terminalId,{owner:r,frames:[]});try{const n=this.manager.openSession({terminalId:e.terminalId,sessionId:e.clientId,command:e.command,args:e.args,env:e.env,cwd:e.cwd,cols:"terminal.open"===e.type?e.columns??80:80,rows:"terminal.open"===e.type?e.rows??24:24,outputLimitBytes:e.outputLimitBytes,initialServerSequence:2,incarnation:i});if(!this.isOwnedTerminal(r))return void this.manager.releaseSessionIfCurrent(e.terminalId,i);this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:n.terminalId,sequence:"1",resumed:!0===t?.recovered,columns:n.cols,rows:n.rows},r);const s=this.openingFrames.get(e.terminalId);s?.owner===r&&this.openingFrames.delete(e.terminalId);for(const e of s?.owner===r?s.frames:[])this.sendOrClose(e,r)}catch(n){throw this.clients.get(e.terminalId)===r&&this.clients.delete(e.terminalId),this.openingFrames.get(e.terminalId)?.owner===r&&this.openingFrames.delete(e.terminalId),this.manager.releaseSessionIfCurrent(e.terminalId,i),n}return}let s=!1;"terminal.resume"===e.type&&(s=this.adoptParkedForResume(e.clientId,e.terminalId,n,t),this.rebindServiceOwnerForResume(e.clientId,e.terminalId,n));const a=this.assertOwner(e.clientId,e.terminalId,n);switch(e.type){case"terminal.resume":{const t=n.outbound.resume(e.clientId,e.terminalId,e.lastServerSequence);if(!this.isOwnedTerminal(a))return;if((t.gap||s)&&this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.output",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(a),stream:"stdout",data:"",encoding:"base64",truncated:!0},a),!this.isOwnedTerminal(a))return;const i=this.manager.getSession(e.terminalId);return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.opened",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(a),resumed:!0,columns:i.cols,rows:i.rows},a)}case"terminal.input":return void this.manager.writeToSessionIfCurrent(e.terminalId,a.incarnation,Buffer.from(e.data,"base64"===e.encoding?"base64":"utf8"));case"terminal.resize":return void this.manager.resizeSessionIfCurrent(e.terminalId,a.incarnation,e.columns,e.rows);case"terminal.close":if(!this.manager.closeSessionIfCurrent(e.terminalId,a.incarnation,e.reason))throw new TerminalNotFoundError(e.terminalId);return;case"terminal.ping":if(this.manager.touchSessionIfCurrent(e.terminalId,a.incarnation),!this.isOwnedTerminal(a))return;return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.pong",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(a),nonce:e.nonce},a);case"terminal.wait":{let n;try{n=await this.manager.waitForExitIfCurrent(e.terminalId,a.incarnation,e.timeoutMs)}catch(e){if(!this.isOwnedTerminal(a))return;throw e}if(!this.isOwnedTerminal(a))return;return void this.sendOrClose({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.wait_result",clientId:e.clientId,terminalId:e.terminalId,sequence:this.nextSequence(a),exitCode:n.exitCode,output:n.output,state:n.state},a)}case"terminal.release":if(!this.manager.isCurrentSession(e.terminalId,a.incarnation))throw new TerminalNotFoundError(e.terminalId);const t=n.outbound.prepareForgetTerminal(e.clientId,e.terminalId);if(!this.manager.detachSessionIfCurrent(e.terminalId,a.incarnation))throw new TerminalNotFoundError(e.terminalId);return this.clients.get(e.terminalId)===a&&this.clients.delete(e.terminalId),this.dropPendingOutput(e.terminalId),void t();case"terminal.kill":if(!this.manager.killSessionIfCurrent(e.terminalId,a.incarnation,normalizeSignal(e.signal)))throw new TerminalNotFoundError(e.terminalId);return}}async execute(e,n){const t=this.outboundGeneration,i=t?.outbound;if(i?.isAvailable&&!i.isAvailable())throw new Error("terminal Service transport is unavailable; legacy fallback is forbidden");const r=e,s=r.context.terminal.terminalId,a=r.context.terminal.clientId;switch(r.name){case"terminal.open":{const e=r,n=this.manager.createSessionIncarnation(s),t=this.manager.openSession({terminalId:s,sessionId:e.context.terminal.clientId,command:e.params.command,args:e.params.args,env:e.params.env,cwd:e.params.cwd,cols:e.params.columns,rows:e.params.rows,outputLimitBytes:e.params.outputLimitBytes,incarnation:n});return this.clients.set(t.terminalId,{kind:"acp",clientId:e.context.terminal.clientId,incarnation:n}),{terminalId:t.terminalId,command:e.params.command??t.shell,shell:t.shell,cwd:t.cwd,columns:t.cols,rows:t.rows,state:t.state}}case"terminal.input":{const e=r,n=this.assertAcpOwner(a,s);return this.manager.writeToSessionIfCurrent(s,n.incarnation,Buffer.from(e.params.data,"base64"===e.params.encoding?"base64":"utf8")),{accepted:!0}}case"terminal.resize":{const e=r,n=this.assertAcpOwner(a,s);return this.manager.resizeSessionIfCurrent(s,n.incarnation,e.params.columns,e.params.rows),{resized:!0}}case"terminal.close":{const e=this.assertAcpOwner(a,s);if(!this.manager.closeSessionIfCurrent(s,e.incarnation,r.params.reason))throw new TerminalNotFoundError(s);return{closed:!0}}case"terminal.ping":{const e=this.assertAcpOwner(a,s);return this.manager.touchSessionIfCurrent(s,e.incarnation),{terminalId:s,status:"ok"}}case"terminal.create":{const e=r,n=this.manager.createSessionIncarnation(s),t=this.manager.openSession({terminalId:s,sessionId:e.context.terminal.clientId,command:e.params.command,args:e.params.args,env:e.params.env,cwd:e.params.cwd,cols:80,rows:24,outputLimitBytes:e.params.outputLimitBytes,incarnation:n});return this.clients.set(t.terminalId,{kind:"acp",clientId:e.context.terminal.clientId,incarnation:n}),{terminalId:t.terminalId,command:e.params.command}}case"terminal.wait":{const e=r,n=this.assertAcpOwner(a,s),t=await this.manager.waitForExitIfCurrent(s,n.incarnation,e.params.timeoutMs);if(!this.isOwnedAcpTerminal(n))throw new TerminalNotFoundError(s);return t}case"terminal.release":{const e=this.assertAcpOwner(a,s);if(!this.manager.detachSessionIfCurrent(s,e.incarnation))throw new TerminalNotFoundError(s);return this.clients.get(s)===e&&this.clients.delete(s),{released:!0}}case"terminal.kill":{const e=this.assertAcpOwner(a,s);if(!this.manager.killSessionIfCurrent(s,e.incarnation,normalizeSignal(r.params.signal)))throw new TerminalNotFoundError(s);return{killed:!0}}}throw new Error(`unsupported terminal command ${r.name}`)}nextSequence(e){return String(this.manager.nextServerSequence(e.incarnation.terminalId,e.incarnation))}sendOrClose(e,n){this.isOwnedTerminal(n)&&!n.generation.outbound.send(e)&&"terminal.output"!==e.type&&this.isOwnedTerminal(n)&&this.manager.closeSessionIfCurrent(e.terminalId,n.incarnation,"backpressure_limit")}emitFrame(e,n){if(!this.isOwnedTerminal(n))return;const t=this.openingFrames.get(e.terminalId);t?.owner===n?t.frames.push(e):this.sendOrClose(e,n)}assertOwner(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.clientId!==e||i.generation!==t||!this.isOwnedTerminal(i))throw new TerminalNotFoundError(n);return i}rebindServiceOwnerForResume(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.generation!==t||!this.isOwnedTerminal(i))return;if(i.clientId===e)return;if(this.flushPendingOutput(n),!this.manager.rebindSessionId(n,i.incarnation,e))throw new TerminalNotFoundError(n);t.outbound.rebindResumeClient(i.clientId,e,n),this.clientSequences.delete(i.clientId);const r={kind:"service",clientId:e,generation:i.generation,incarnation:i.incarnation};this.clients.set(n,r)}adoptParkedForResume(e,n,t,i){const r=this.parked.get(n);return!(!r||!0!==i?.recovered||this.clients.has(n))&&(clearTimeout(r.timer),this.parked.delete(n),"running"!==this.manager.getSession(n)?.state||r.clientId!==e&&!this.manager.rebindSessionId(n,r.incarnation,e)?(this.retireParked(n,r,"runtime_lease_lost"),!1):(this.clients.set(n,{kind:"service",clientId:e,generation:t,incarnation:r.incarnation}),!0))}closeParked(e,n){const t=this.parked.get(e);t&&(clearTimeout(t.timer),this.parked.delete(e),this.retireParked(e,t,n))}retireParked(e,n,t){"closed"===this.manager.getSession(e)?.state?this.manager.releaseSessionIfCurrent(e,n.incarnation):this.manager.closeSessionIfCurrent(e,n.incarnation,t)}assertAcpOwner(e,n){const t=this.clients.get(n);if("acp"!==t?.kind||t.clientId!==e||!this.isOwnedAcpTerminal(t))throw new TerminalNotFoundError(n);return t}isGenerationActive(e){return this.outboundGeneration===e}isOwnedTerminal(e){return this.outboundGeneration===e.generation&&this.clients.get(e.incarnation.terminalId)===e&&this.manager.isCurrentSession(e.incarnation.terminalId,e.incarnation)}isOwnedAcpTerminal(e){return this.clients.get(e.incarnation.terminalId)===e&&this.manager.isCurrentSession(e.incarnation.terminalId,e.incarnation)}expireServiceTerminalReplay(e,n,t){const i=this.clients.get(n);if("service"!==i?.kind||i.clientId!==e||i.generation!==t||!this.isOwnedTerminal(i))return;const r=t.outbound.prepareForgetTerminal(e,n);if(!("closed"===this.manager.getSession(n)?.state?this.manager.releaseSessionIfCurrent(n,i.incarnation):this.manager.detachSessionIfCurrent(n,i.incarnation)))throw new TerminalNotFoundError(n);this.clients.get(n)===i&&this.clients.delete(n),this.openingFrames.get(n)?.owner===i&&this.openingFrames.delete(n),this.dropPendingOutput(n),r()}clearGenerationOwnership(e){for(const[n,t]of this.clients)"service"===t.kind&&t.generation===e&&this.clients.delete(n);for(const[n,t]of this.clientSequences)t.generation===e&&this.clientSequences.delete(n);for(const[n,t]of this.openingFrames)t.owner.generation===e&&this.openingFrames.delete(n);for(const[n,t]of this.pendingOutput)t.owner.generation===e&&this.dropPendingOutput(n)}queueOutput(e,n,t,i,r){let s=0,a=i;for(;s<t.length;){let i=this.pendingOutput.get(e);!i||i.owner===r&&i.stream===n||(this.flushPendingOutput(e),i=void 0),i||(i={owner:r,stream:n,chunks:[],bytes:0,sequence:a},this.pendingOutput.set(e,i));const o=Math.min(16384-i.bytes,t.length-s);i.chunks.push(t.subarray(s,s+o)),i.bytes+=o,i.sequence=a,s+=o,16384!==i.bytes?i.timer||(i.timer=setTimeout(()=>{this.pendingOutput.get(e)===i&&this.flushPendingOutput(e)},16),i.timer.unref?.()):(this.flushPendingOutput(e),s<t.length&&(a=this.manager.nextServerSequence(e,r.incarnation)))}}flushPendingOutput(e){const n=this.pendingOutput.get(e);if(!n)return;if(this.pendingOutput.delete(e),n.timer&&clearTimeout(n.timer),0===n.bytes||!this.isOwnedTerminal(n.owner))return;const t=1===n.chunks.length?n.chunks[0]:Buffer.concat(n.chunks,n.bytes);this.emitFrame({protocolVersion:TERMINAL_SERVICE_PROTOCOL_VERSION,type:"terminal.output",clientId:n.owner.clientId,terminalId:e,sequence:String(n.sequence),stream:n.stream,data:t.toString("base64"),encoding:"base64"},n.owner)}dropPendingOutput(e){const n=this.pendingOutput.get(e);n&&(n.timer&&clearTimeout(n.timer),this.pendingOutput.delete(e))}}const runtimeServices=new WeakMap;export function getOrCreateRuntimeTerminalService(e){let n=runtimeServices.get(e);return n||(n=new RuntimeTerminalService(e),runtimeServices.set(e,n)),n}export function assertTerminalClientFrame(e){const n=e;if(!(n&&CLIENT_FRAME_TYPES.has(String(n.type))&&n.protocolVersion===TERMINAL_SERVICE_PROTOCOL_VERSION&&isUuid(n.clientId)&&isUuid(n.terminalId)&&isPositiveRuntimeSequence(n.clientSequence)))throw new Error("invalid terminal client frame");switch(e.type){case"terminal.open":assertOptionalCommand(e.command),assertOptionalArgs(e.args),assertOptionalEnv(e.env),assertOptionalString(e.cwd),assertOptionalDimension(e.columns),assertOptionalDimension(e.rows),assertOptionalOutputLimit(e.outputLimitBytes);break;case"terminal.create":("string"!=typeof e.command||e.command.length<1||e.command.length>4096)&&invalidFrame(),assertOptionalArgs(e.args),assertOptionalEnv(e.env),assertOptionalString(e.cwd),assertOptionalOutputLimit(e.outputLimitBytes);break;case"terminal.resume":isRuntimeSequence(e.lastServerSequence)||invalidFrame();break;case"terminal.input":("string"!=typeof e.data||"utf8"!==e.encoding&&"base64"!==e.encoding)&&invalidFrame();break;case"terminal.resize":assertDimension(e.columns),assertDimension(e.rows);break;case"terminal.close":assertOptionalString(e.reason);break;case"terminal.ping":"string"!=typeof e.nonce&&invalidFrame();break;case"terminal.wait":void 0!==e.timeoutMs&&(!Number.isInteger(e.timeoutMs)||e.timeoutMs<0||e.timeoutMs>864e5)&&invalidFrame();break;case"terminal.kill":assertOptionalString(e.signal)}}const CLIENT_FRAME_TYPES=new Set(["terminal.open","terminal.resume","terminal.input","terminal.resize","terminal.close","terminal.ping","terminal.create","terminal.wait","terminal.release","terminal.kill"]);function normalizeSignal(e){return e&&e.startsWith("SIG")?e:"SIGTERM"}const UUID_PATTERN=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,UINT64_MAX=18446744073709551615n;function isUuid(e){return"string"==typeof e&&UUID_PATTERN.test(e)}function isRuntimeSequence(e){return"string"==typeof e&&/^(0|[1-9][0-9]*)$/.test(e)&&BigInt(e)<=UINT64_MAX}function isPositiveRuntimeSequence(e){return isRuntimeSequence(e)&&"0"!==e}function invalidFrame(){throw new Error("invalid terminal client frame")}function assertOptionalString(e){void 0!==e&&"string"!=typeof e&&invalidFrame()}function assertOptionalCommand(e){void 0!==e&&("string"!=typeof e||e.length<1||e.length>4096)&&invalidFrame()}function assertOptionalArgs(e){void 0===e||Array.isArray(e)&&e.every(e=>"string"==typeof e)||invalidFrame()}function assertOptionalEnv(e){void 0===e||e&&"object"==typeof e&&!Array.isArray(e)&&Object.values(e).every(e=>"string"==typeof e)||invalidFrame()}function assertDimension(e){(!Number.isInteger(e)||e<1||e>1e3)&&invalidFrame()}function assertOptionalDimension(e){void 0!==e&&assertDimension(e)}function assertOptionalOutputLimit(e){void 0!==e&&(!Number.isInteger(e)||e<1||e>16777216)&&invalidFrame()}
@@ -1 +1 @@
1
- import{createHash,randomUUID}from"node:crypto";import{EventEmitter}from"node:events";import WebSocket from"ws";import{createTerminalAgentAuth,terminalAgentAuthSigningMessage as harnessTerminalAgentAuthSigningMessage,terminalAgentWebSocketUrl}from"@beeos-ai/cloud-harness-sdk";export function terminalAgentAuthSigningMessage(e){return harnessTerminalAgentAuthSigningMessage({publicKey:e.public_key,timestamp:e.timestamp,nonce:e.nonce,runtimeLeaseCredential:e.runtimeLeaseCredential,handlerIdentity:e.handlerIdentity,runtimeEpoch:e.runtimeEpoch,leaseId:e.leaseId})}export function createTerminalLeaseBoundAgentAuth(e){if(!(32===e.keys.publicKey.length&&[32,48].includes(e.keys.privateKey.length)&&e.lease.instanceId&&e.lease.leaseId&&e.lease.handlerIdentity&&e.lease.runtimeLeaseCredential&&isPositiveRuntimeEpoch(e.lease.runtimeEpoch)&&Number.isSafeInteger(e.timestamp)&&!(e.timestamp<0)&&e.nonce))throw new Error("invalid Terminal runtime lease authority binding");return createTerminalAgentAuth({lease:{instanceId:e.lease.instanceId,leaseId:e.lease.leaseId,runtimeEpoch:e.lease.runtimeEpoch,runtimeLeaseCredential:e.lease.runtimeLeaseCredential,handlerIdentity:e.lease.handlerIdentity},keys:e.keys,timestamp:e.timestamp,nonce:e.nonce})}export class TerminalLeaseBoundBridgeClient extends EventEmitter{options;state="idle";socket;unsubscribe;expiryTimer;reconnectTimer;generation=0;stopped=!0;authenticatedBindingHash;authorityEpochBindingHash;transition=Promise.resolve();constructor(e){super(),this.options=e}async connect(){this.unsubscribe||(this.unsubscribe=this.options.authority.subscribeAuthority(e=>this.onAuthorityChanged(e))),this.stopped=!1;const e=this.options.authority.authorityLease;if(!e)throw new Error("active runtime registration lease required for Terminal auth");await this.queueTransition(()=>this.replaceConnection(e))}async send(e){const t=this.socket;if("connected"!==this.state||!t||t.readyState!==WebSocket.OPEN)throw new Error("Terminal lease-bound Bridge is not connected");await new Promise((i,n)=>t.send(e,e=>e?n(e):i()))}async close(e=!0){this.stopped=!0,this.clearTimers(),this.unsubscribe?.(),this.unsubscribe=void 0,this.authenticatedBindingHash=void 0,this.authorityEpochBindingHash=void 0,this.generation++,await this.closeSocket(e),this.state="closed"}onAuthorityChanged(e){if(this.stopped)return;if(!e)return void this.fenceAuthority();this.scheduleExpiryFence(e);const t=authorityEpochHash(e),i=authorityBindingHash(e);return this.authorityEpochBindingHash&&t!==this.authorityEpochBindingHash?(this.authorityEpochBindingHash=t,this.emit("transportEpochChanged"),void this.queueTransition(()=>this.replaceConnection(e)).catch(e=>this.emitError(e))):i!==this.authenticatedBindingHash?"connected"===this.state&&this.authenticatedBindingHash?(this.authenticatedBindingHash=i,void(this.authorityEpochBindingHash??=t)):void this.queueTransition(()=>this.replaceConnection(e)).catch(e=>this.emitError(e)):void 0}async replaceConnection(e){if(this.stopped||null===this.options.authority.authorityLease)return;this.clearReconnectTimer();const t=++this.generation;if(await this.closeSocket(!1),this.stopped||t!==this.generation)return;this.state="connecting";const i=new WebSocket(terminalAgentWebSocketUrl(this.options.bridgeUrl));this.socket=i;const n=authorityBindingHash(e);await new Promise((s,o)=>{let r=!1;const a=setTimeout(()=>{c(new Error("Terminal auth timeout")),i.readyState!==WebSocket.CLOSED&&i.terminate()},this.options.authTimeoutMs??1e4),h=()=>{clearTimeout(a),i.off("open",u)},c=e=>{h(),r||o(e)},u=()=>{const e=this.options.authority.authorityLease;if(!e||authorityBindingHash(e)!==n||t!==this.generation)return c(new Error("Terminal runtime lease changed during auth")),void i.close();const s=createTerminalLeaseBoundAgentAuth({lease:e,keys:this.options.keys,timestamp:Math.floor((this.options.now??Date.now)()/1e3),nonce:(this.options.nonce??randomUUID)()});i.send(JSON.stringify(s),e=>{e&&c(e)})};i.on("open",u),i.on("message",o=>{if(!r){try{if("auth_ok"!==JSON.parse(o.toString()).type)return}catch{return}return r=!0,h(),t!==this.generation?void i.close():(this.authenticatedBindingHash=n,this.authorityEpochBindingHash=authorityEpochHash(e),this.state="connected",this.scheduleExpiryFence(e),this.emit("connected"),void s())}this.emit("message",o)}),i.on("error",e=>{this.emitError(e),c(e)}),i.on("close",(e,i)=>{h(),r||o(new Error(`Terminal auth rejected: ${i.toString()||"connection closed"}`)),t===this.generation&&(this.socket=void 0,this.authenticatedBindingHash=void 0,this.state="idle",this.emit("disconnected",i.toString()||"connection closed"),1008!==e?!this.stopped&&this.options.authority.authorityLease&&this.scheduleReconnect():this.fenceAuthority())})})}fenceAuthority(){this.clearTimers(),this.authenticatedBindingHash=void 0,this.authorityEpochBindingHash=void 0,this.generation++;const e=this.socket;this.socket=void 0,this.state="idle",e&&e.readyState!==WebSocket.CLOSED&&e.terminate(),this.emit("authorityLost")}scheduleExpiryFence(e){this.expiryTimer&&clearTimeout(this.expiryTimer);const t=authorityBindingHash(e);this.expiryTimer=setTimeout(()=>{const e=this.options.authority.authorityLease;e?authorityBindingHash(e)===t&&(Date.parse(e.leaseExpiresAt)>(this.options.now??Date.now)()?this.scheduleExpiryFence(e):this.fenceAuthority()):this.fenceAuthority()},Math.min(2147483647,Math.max(0,Date.parse(e.leaseExpiresAt)-(this.options.now??Date.now)()))),this.expiryTimer.unref?.()}scheduleReconnect(){this.reconnectTimer||this.stopped||(this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=void 0;const e=this.options.authority.authorityLease;e&&this.queueTransition(()=>this.replaceConnection(e)).catch(e=>{this.emitError(e),this.scheduleReconnect()})},this.options.reconnectDelayMs??1e3),this.reconnectTimer.unref?.())}async closeSocket(e){const t=this.socket;this.socket=void 0,t&&t.readyState!==WebSocket.CLOSED&&(e&&t.readyState===WebSocket.OPEN?await new Promise(e=>{t.once("close",()=>e()),t.close()}):t.terminate())}queueTransition(e){const t=this.transition.then(e,e);return this.transition=t.catch(()=>{}),t}clearReconnectTimer(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0}clearTimers(){this.clearReconnectTimer(),this.expiryTimer&&clearTimeout(this.expiryTimer),this.expiryTimer=void 0}emitError(e){this.listenerCount("error")>0&&this.emit("error",e instanceof Error?e:new Error(String(e)))}}function authorityEpochHash(e){return createHash("sha256").update(`${e.instanceId}\0${e.leaseId}\0${e.handlerIdentity}\0${e.runtimeEpoch}`).digest("hex")}function authorityBindingHash(e){return createHash("sha256").update(`${e.instanceId}\0${e.leaseId}\0${e.handlerIdentity}\0${e.runtimeEpoch}\0${e.runtimeLeaseCredential}\0${e.leaseExpiresAt}`).digest("hex")}function isPositiveRuntimeEpoch(e){return"string"==typeof e&&/^[1-9][0-9]*$/.test(e)&&BigInt(e)<=18446744073709551615n}
1
+ import{createHash,randomUUID}from"node:crypto";import{EventEmitter}from"node:events";import WebSocket from"ws";import{createTerminalAgentAuth,terminalAgentAuthSigningMessage as harnessTerminalAgentAuthSigningMessage,terminalAgentWebSocketUrl}from"@beeos-ai/cloud-harness-sdk";export function terminalAgentAuthSigningMessage(e){return harnessTerminalAgentAuthSigningMessage({publicKey:e.public_key,timestamp:e.timestamp,nonce:e.nonce,runtimeLeaseCredential:e.runtimeLeaseCredential,handlerIdentity:e.handlerIdentity,runtimeEpoch:e.runtimeEpoch,leaseId:e.leaseId})}export function createTerminalLeaseBoundAgentAuth(e){if(!(32===e.keys.publicKey.length&&[32,48].includes(e.keys.privateKey.length)&&e.lease.instanceId&&e.lease.leaseId&&e.lease.handlerIdentity&&e.lease.runtimeLeaseCredential&&isPositiveRuntimeEpoch(e.lease.runtimeEpoch)&&Number.isSafeInteger(e.timestamp)&&!(e.timestamp<0)&&e.nonce))throw new Error("invalid Terminal runtime lease authority binding");return createTerminalAgentAuth({lease:{instanceId:e.lease.instanceId,leaseId:e.lease.leaseId,runtimeEpoch:e.lease.runtimeEpoch,runtimeLeaseCredential:e.lease.runtimeLeaseCredential,handlerIdentity:e.lease.handlerIdentity},keys:e.keys,timestamp:e.timestamp,nonce:e.nonce})}export class TerminalLeaseBoundBridgeClient extends EventEmitter{options;state="idle";socket;unsubscribe;expiryTimer;reconnectTimer;generation=0;stopped=!0;authenticatedBindingHash;authorityEpochBindingHash;transition=Promise.resolve();constructor(e){super(),this.options=e}async connect(){this.unsubscribe||(this.unsubscribe=this.options.authority.subscribeAuthority((e,t)=>this.onAuthorityChanged(e,t))),this.stopped=!1;const e=this.options.authority.authorityLease;if(!e)throw new Error("active runtime registration lease required for Terminal auth");await this.queueTransition(()=>this.replaceConnection(e))}async send(e){const t=this.socket;if("connected"!==this.state||!t||t.readyState!==WebSocket.OPEN)throw new Error("Terminal lease-bound Bridge is not connected");await new Promise((i,n)=>t.send(e,e=>e?n(e):i()))}async close(e=!0){this.stopped=!0,this.clearTimers(),this.unsubscribe?.(),this.unsubscribe=void 0,this.authenticatedBindingHash=void 0,this.authorityEpochBindingHash=void 0,this.generation++,await this.closeSocket(e),this.state="closed"}onAuthorityChanged(e,t){if(this.stopped)return;if(!e)return void this.fenceAuthority(void 0===t);this.scheduleExpiryFence(e);const i=authorityEpochHash(e),n=authorityBindingHash(e);return this.authorityEpochBindingHash&&i!==this.authorityEpochBindingHash?(this.authorityEpochBindingHash=i,this.emit("transportEpochChanged"),void this.queueTransition(()=>this.replaceConnection(e)).catch(e=>this.emitError(e))):n!==this.authenticatedBindingHash?"connected"===this.state&&this.authenticatedBindingHash?(this.authenticatedBindingHash=n,void(this.authorityEpochBindingHash??=i)):void this.queueTransition(()=>this.replaceConnection(e)).catch(e=>this.emitError(e)):void 0}async replaceConnection(e){if(this.stopped||null===this.options.authority.authorityLease)return;this.clearReconnectTimer();const t=++this.generation;if(await this.closeSocket(!1),this.stopped||t!==this.generation)return;this.state="connecting";const i=new WebSocket(terminalAgentWebSocketUrl(this.options.bridgeUrl));this.socket=i;const n=authorityBindingHash(e);await new Promise((s,r)=>{let o=!1;const a=setTimeout(()=>{c(new Error("Terminal auth timeout")),i.readyState!==WebSocket.CLOSED&&i.terminate()},this.options.authTimeoutMs??1e4),h=()=>{clearTimeout(a),i.off("open",u)},c=e=>{h(),o||r(e)},u=()=>{const e=this.options.authority.authorityLease;if(!e||authorityBindingHash(e)!==n||t!==this.generation)return c(new Error("Terminal runtime lease changed during auth")),void i.close();const s=createTerminalLeaseBoundAgentAuth({lease:e,keys:this.options.keys,timestamp:Math.floor((this.options.now??Date.now)()/1e3),nonce:(this.options.nonce??randomUUID)()});i.send(JSON.stringify(s),e=>{e&&c(e)})};i.on("open",u),i.on("message",r=>{if(!o){try{if("auth_ok"!==JSON.parse(r.toString()).type)return}catch{return}return o=!0,h(),t!==this.generation?void i.close():(this.authenticatedBindingHash=n,this.authorityEpochBindingHash=authorityEpochHash(e),this.state="connected",this.scheduleExpiryFence(e),this.emit("connected",{handlerIdentity:e.handlerIdentity}),void s())}this.emit("message",r)}),i.on("error",e=>{this.emitError(e),c(e)}),i.on("close",(e,i)=>{h(),o||r(new Error(`Terminal auth rejected: ${i.toString()||"connection closed"}`)),t===this.generation&&(this.socket=void 0,this.authenticatedBindingHash=void 0,this.state="idle",this.emit("disconnected",i.toString()||"connection closed"),1008!==e?!this.stopped&&this.options.authority.authorityLease&&this.scheduleReconnect():this.fenceAuthority(!1))})})}fenceAuthority(e){this.clearTimers(),this.authenticatedBindingHash=void 0,this.authorityEpochBindingHash=void 0,this.generation++;const t=this.socket;this.socket=void 0,this.state="idle",t&&t.readyState!==WebSocket.CLOSED&&t.terminate(),this.emit("authorityLost",{recoverable:e})}scheduleExpiryFence(e){this.expiryTimer&&clearTimeout(this.expiryTimer);const t=authorityBindingHash(e);this.expiryTimer=setTimeout(()=>{const e=this.options.authority.authorityLease;e?authorityBindingHash(e)===t&&(Date.parse(e.leaseExpiresAt)>(this.options.now??Date.now)()?this.scheduleExpiryFence(e):this.fenceAuthority(!0)):this.fenceAuthority(!0)},Math.min(2147483647,Math.max(0,Date.parse(e.leaseExpiresAt)-(this.options.now??Date.now)()))),this.expiryTimer.unref?.()}scheduleReconnect(){this.reconnectTimer||this.stopped||(this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=void 0;const e=this.options.authority.authorityLease;e&&this.queueTransition(()=>this.replaceConnection(e)).catch(e=>{this.emitError(e),this.scheduleReconnect()})},this.options.reconnectDelayMs??1e3),this.reconnectTimer.unref?.())}async closeSocket(e){const t=this.socket;this.socket=void 0,t&&t.readyState!==WebSocket.CLOSED&&(e&&t.readyState===WebSocket.OPEN?await new Promise(e=>{t.once("close",()=>e()),t.close()}):t.terminate())}queueTransition(e){const t=this.transition.then(e,e);return this.transition=t.catch(()=>{}),t}clearReconnectTimer(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0}clearTimers(){this.clearReconnectTimer(),this.expiryTimer&&clearTimeout(this.expiryTimer),this.expiryTimer=void 0}emitError(e){this.listenerCount("error")>0&&this.emit("error",e instanceof Error?e:new Error(String(e)))}}function authorityEpochHash(e){return createHash("sha256").update(`${e.instanceId}\0${e.leaseId}\0${e.handlerIdentity}\0${e.runtimeEpoch}`).digest("hex")}function authorityBindingHash(e){return createHash("sha256").update(`${e.instanceId}\0${e.leaseId}\0${e.handlerIdentity}\0${e.runtimeEpoch}\0${e.runtimeLeaseCredential}\0${e.leaseExpiresAt}`).digest("hex")}function isPositiveRuntimeEpoch(e){return"string"==typeof e&&/^[1-9][0-9]*$/.test(e)&&BigInt(e)<=18446744073709551615n}
@@ -0,0 +1 @@
1
+ import{prepareNativeOutputTool}from"../runtime/native-output-file-attempt.js";import{RENDER_UI_TOOL_NAME}from"../canvas/tools.js";import{BEEOS_UPLOAD_TOOL_NAME}from"./tool.js";export function needsNativeOutputCapability(t,o){return t===BEEOS_UPLOAD_TOOL_NAME||t===RENDER_UI_TOOL_NAME&&Object.prototype.hasOwnProperty.call(o,"html")}export function nativeOutputToolCallGate(t,o){if(needsNativeOutputCapability(t.toolName,t.params)){if(t.runId&&o.runId&&t.runId!==o.runId||t.toolCallId&&o.toolCallId&&t.toolCallId!==o.toolCallId)return{block:!0,blockReason:"native output file Host correlation conflict"};try{prepareNativeOutputTool(t.runId??o.runId,t.toolCallId??o.toolCallId)}catch{return{block:!0,blockReason:"native output file trusted capability unavailable"}}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.6.63-cloud.13",
3
+ "version": "0.6.63-cloud.15",
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>",
@@ -88,5 +88,5 @@
88
88
  "fast-uri": ">=3.1.4",
89
89
  "brace-expansion": ">=5.0.9"
90
90
  },
91
- "gitHead": "be153a0b4e848144e212e10b6cd09f8fbc010abe"
91
+ "gitHead": "f904ee325272318fce36928daa480123d62fed16"
92
92
  }
@@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises";
2
2
  import { createHash } from "node:crypto";
3
3
  import { join } from "node:path";
4
4
  import { GatewayClient } from "../dist/gateway-client.js";
5
- import { loadAgentKeyPair, agentBoundFetch } from "../dist/agent-auth.js";
5
+ import { agentIdentityFromKeyPair, authenticatedAgentFetch, loadAgentKeyPair } from "@beeos-ai/cloud-harness-sdk";
6
6
  import { OpenClawRuntimeCapabilities } from "../dist/runtime/openclaw-runtime-capabilities.js";
7
7
  import { OpenClawRuntimeCommandDispatcher } from "../dist/runtime/runtime-command-dispatcher.js";
8
8
  import { FileRuntimeAgentIdentityRegistry } from "../dist/runtime/runtime-agent-identity-registry.js";
@@ -48,6 +48,7 @@ export async function main() {
48
48
  if (agentKeys.publicKey.length !== 32 || agentKeys.privateKey.length !== 32) {
49
49
  throw new Error("blackbox Agent identity must contain 32-byte Ed25519 public/seed keys");
50
50
  }
51
+ const agentIdentity = agentIdentityFromKeyPair(agentKeys);
51
52
  const leaseClaims = decodeRuntimeLease(nonempty(authority.runtimeLeaseCredential,
52
53
  "authority.runtimeLeaseCredential"));
53
54
  const instanceId = requiredEnvironment("FIXTURE_INSTANCE_ID");
@@ -101,11 +102,13 @@ export async function main() {
101
102
  });
102
103
  const messageFetch = messageComposition.nodeTransport;
103
104
  const boundAgentFetch = async (url, init = {}) => {
105
+ // The Gateway v2 signing preimage covers the path only, so a query would be unsigned.
106
+ if (new URL(url).search !== "") throw new Error("body-bound agent authority routes forbid query parameters");
104
107
  const headers = new Headers(init.headers);
105
108
  headers.set("X-Instance-ID", instanceId);
106
109
  headers.set("X-Instance-Owner-ID", ownerId);
107
110
  httpRequests += 1;
108
- return await withinDeadline((signal) => agentBoundFetch(url, agentKeys, { ...init, headers, signal }), deadline);
111
+ return await withinDeadline((signal) => authenticatedAgentFetch(url, agentIdentity, { ...init, headers, signal }), deadline);
109
112
  };
110
113
  const statePrefix = join(receiptDir, "claw-worker");
111
114
  const commandJournal = new FileRuntimeCommandJournal(`${statePrefix}-commands.json`);