beeos-claw 0.6.63-cloud.12 → 0.6.63-cloud.14

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{randomUUID,createHash}from"node:crypto";import{mkdir,readFile,rm,stat,readdir,rename,cp}from"node:fs/promises";import path from"node:path";import{join,resolve,relative,sep}from"node:path";import{homedir}from"node:os";import AdmZip from"adm-zip";import{classifyCatalogSessionKey,prepareOpenClawToolCatalog}from"../runtime/prepare-openclaw-tool-catalog.js";import{sanitizePromptPayload}from"../message-sanitization/prompt-payload-sanitizer.js";import{previewSanitizedText,stripTransportMetadata}from"../message-sanitization/transport-metadata-sanitizer.js";import{isHeartbeatPromptText,isCronPromptText,extractCronJobId,classifyHeartbeatFilter}from"../message-sanitization/message-classifier.js";import{createCronIdentityStore,createInMemoryCronIdentityStore}from"../cron-identities.js";import{CANVAS_UPDATE_HINT,buildActiveSurfacesContext}from"../canvas/tools.js";import{parseMentionsParam,buildMentionInstruction}from"../delegation/mention-resolver.js";import{EXTERNAL_PROTOCOL_TAGS}from"./external-protocol-tags.js";import{extractPromptBlocks,toGatewayPrompt,resolveSlashCommandName as resolveSlashCmd}from"./prompt-converter.js";import{extractBeeosUserTextFromHistoryContent}from"./beeos-user-envelope.js";import{loadHistory,loadHistoryPage,groupIntoTurns,splitUserReplayBlocks}from"./history-replay.js";import{classifyHistoricalAssistantTurnCompletion}from"./history-turn-completion-policy.js";import{LocalSessionHistory}from"./local-session-history.js";import{reconcileMissingArtifacts}from"./missing-artifacts-reconciler.js";import{AcpRunOutputChannel}from"./acp-run-output-channel.js";import{buildResolutionPlan,resolveResources,applyResolutions}from"./prompt-resource-resolver.js";import{readEnrichmentCache,readUploadToolResult}from"../upload/result-cache.js";import{buildToolResultSessionContentBlocks}from"./tool-result-blocks.js";import{prefersChatFileArtifacts}from"./file-artifact-presenter.js";import{buildToolCallPayload,buildToolCallUpdatePayload}from"./tool-call-payload-builder.js";import{Errors}from"../errors/index.js";import{classifyAgentRuntimeFailure,INTERRUPTED_PUBLIC_MESSAGE}from"../runtime/agent-runtime-failure-classifier.js";import{isPlaceholderTitle}from"../runtime/conversation-title.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"../runtime/session-mutation-coordinator.js";import{observeLifecyclePromise}from"../runtime/lifecycle-promise-observer.js";import{RunOutputRouter,errorRunOutput}from"../runtime/run-output-router.js";import{AgentMappingRegistry}from"../runtime/agent-mapping-registry.js";import{RuntimeSessionNewRejectedError}from"../runtime/runtime-rpc-consumer.js";import{normalizeRuntimeAgentLocalKey,OpenClawRuntimeCommandDispatcher,RuntimeCommandError}from"../runtime/runtime-command-dispatcher.js";import{FileRuntimeAgentIdentityRegistry}from"../runtime/runtime-agent-identity-registry.js";import{FileRuntimeLeasePort}from"../runtime/runtime-operation-lease.js";import{FileRuntimeCommandJournal,InMemoryRuntimeCommandJournal}from"../runtime/runtime-command-journal.js";import{OpenClawRuntimeCapabilities}from"../runtime/openclaw-runtime-capabilities.js";import{RuntimeCanvasService}from"../canvas/runtime-canvas-service.js";import{RuntimeSkillsService}from"../runtime/runtime-skills-service.js";import{RuntimeCronService,encodeBeeosChannelTag}from"../runtime/runtime-cron-service.js";export{BEEOS_CHANNEL_TAG_RE,decodeBeeosChannelId,encodeBeeosChannelTag,stripBeeosChannelTag}from"../runtime/runtime-cron-service.js";import{openClawKeyMatchesConversation,openClawSessionKeysFromList,resolveLiveOpenClawSessionKey}from"../runtime/openclaw-session-key-resolution.js";import{LocalConversationHistoryBoundary,OpenClawGatewaySessionMetadataPort,OpenClawSessionMetadataService,RuntimeSessionControlService}from"../runtime/runtime-session-control-service.js";import{pageRuntimeSessions,parseRuntimeSessionListPage}from"../runtime/runtime-session-list-policy.js";import{assertTrustedCoreExecutionAttempt,createAcpCoreExecutionAttempt}from"../runtime/core-execution-attempt-context.js";import{CompositeActiveRunPort}from"../runtime/composite-active-run-port.js";import{resolveChannelFromSessionKey}from"../channel-detection.js";import{resolveTurnId,recordTurnIdForRole,recordToolCallTurnForUpdate}from"./gateway-events.js";import{emitToolResultProjectionForTurn}from"./tool-result-emitter.js";import{extractPersistedToolResultEnvelope,findPersistedToolResultEnvelopeByMessageId}from"./tool-result-transcript.js";import{projectPersistedToolResultForReplay}from"./tool-result-projection.js";import{openRunOutputRoute}from"./run-output-route.js";import{McpService}from"./mcp-service.js";import{McpDesiredStateStore,normalizeMcpPublicServerConfig}from"./mcp-desired-state-store.js";import{requireSecretFreeMcpServerName}from"./mcp-public-config-security.js";import{McpDesiredStateController}from"./mcp-desired-state-controller.js";import{CredentialReferenceRuntimeResolver}from"./mcp-credential-reference-port.js";import{ACP_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/acp";import{canonicalizeJcs}from"@beeos-ai/beeos-types/runtime";import{retainedVerifiedNativePrompt}from"../runtime/native-prompt-authority.js";import{MAX_ZIP_BYTES,parseAndValidateTemplatePackage,TemplatePackageError}from"./agent-template-package.js";const DEFAULT_AGENT_ID="main";export function normalizeAgentId(e){return normalizeRuntimeAgentLocalKey(e)}function narrowContentBlock(e){switch(e.type){case"text":return"string"==typeof e.text?{type:"text",text:e.text}:void 0;case"resource_link":return"string"==typeof e.uri?{type:"resource_link",uri:e.uri,...void 0!==e.name?{name:e.name}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}}:void 0;case"file":return{type:"file",...void 0!==e.text?{text:e.text}:{},...void 0!==e.uri?{uri:e.uri}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}};case"image":return{type:"image",...void 0!==e.uri?{uri:e.uri}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}};default:return}}const DEFAULT_MODE={id:"default",name:"Default",description:"Default agent mode"},USER_MESSAGE_PREFIX="User Message From BeeOS:",USER_MESSAGE_PREFIX_WITH_NEWLINE=`${USER_MESSAGE_PREFIX}\n`,SLASH_COMMAND_NAME_RE=/^[a-z0-9_-]+$/i,HISTORY_PAGE_DEFAULT_LIMIT=100,HISTORY_PAGE_MAX_LIMIT=1e3;function withUserMessagePrefix(e){return e.startsWith(USER_MESSAGE_PREFIX)?e:`${USER_MESSAGE_PREFIX_WITH_NEWLINE}${e}`}const BACKGROUND_SESSION_PATTERNS=[":cron:",":hook:","node-",...EXTERNAL_PROTOCOL_TAGS];function isBackgroundSessionKey(e){return BACKGROUND_SESSION_PATTERNS.some(t=>e.includes(t))}function isSystemDefaultSessionKey(e){const t=e.split(":");if(t.length<3||"agent"!==t[0])return!1;const s=t.slice(2).join(":");return"main"===s||s===t[1]}function cleanGatewayDerivedTitle(e){if(!e)return;let t=extractBeeosUserTextFromHistoryContent(e).text;return/^\[System\b/.test(t)?void 0:(t=t.trim(),t||void 0)}function cleanHistoryUserContent(e){return extractBeeosUserTextFromHistoryContent(e).text.trim()}function mergePersistedToolResultEnvelopes(e,t){if(e||t)return{toolName:e?.toolName||t?.toolName||"tool",...e?.toolCallId||t?.toolCallId?{toolCallId:e?.toolCallId??t?.toolCallId}:{},...e?.rawText??t?.rawText?{rawText:e?.rawText??t?.rawText}:{},...void 0!==e?.rawResult||void 0!==t?.rawResult?{rawResult:e?.rawResult??t?.rawResult}:{},...e?.timestamp||t?.timestamp?{timestamp:e?.timestamp??t?.timestamp}:{}}}function resolveStandaloneSlashCommandName(e,t){if(0===e.length)return;if(e.some(e=>"text"!==e.type))return;const s=t.trim();if(!s||s.includes("\n")||!s.startsWith("/"))return;const i=s.match(/^\/([^\s:]+)(?::|\s|$)/)?.[1]?.trim();return i&&!i.includes("/")&&SLASH_COMMAND_NAME_RE.test(i)?i.toLowerCase():void 0}class AcpRuntimeCommandResourceStore{fetchPackage;parsePackage;isValidationError;content=new Map;artifacts=new Map;mcpDefinitions=new Map;constructor(e,t,s){this.fetchPackage=e,this.parsePackage=t,this.isValidationError=s}registerContent(e){const t=`acp-content:sha256:${createHash("sha256").update(e).digest("hex")}`;return this.content.set(t,e),t}registerMcpDefinition(e,t){this.mcpDefinitions.set(JSON.stringify(e),structuredClone(t))}async resolveMcpDefinition(e){const t=this.mcpDefinitions.get(JSON.stringify(e));if(!t)throw new Error("ACP MCP definition was not verified for this request");return structuredClone(t)}async registerArtifact(e,t){try{const s=await this.fetchPackage(e);if(!s.ok)throw new TemplatePackageError(`failed to download package: HTTP ${s.status}`);const i=Buffer.from(await s.arrayBuffer());if(i.length>MAX_ZIP_BYTES)throw new TemplatePackageError(`package too large: ${i.length} bytes (max ${MAX_ZIP_BYTES})`);const n=createHash("sha256").update(i).digest("hex");if(t?.trim()&&t.trim().toLowerCase()!==n)throw new TemplatePackageError("package sha256 mismatch");const o=this.parsePackage(i),r=`acp-artifact:sha256:${n}`;return this.artifacts.set(r,o),{artifactRef:r,version:o.manifest?.version??"0.0.0",sha256:n,sizeBytes:String(i.length),source:"acp-verified"}}catch(e){if(e instanceof TemplatePackageError||this.isValidationError(e))throw new RuntimeCommandError("INVALID_PARAMS",`Invalid template package: ${e instanceof Error?e.message:String(e)}`);throw new RuntimeCommandError("EXECUTION_FAILED",`Apply template failed: ${e instanceof Error?e.message:String(e)}`)}}registerParsedArtifact(e){const t=createHash("sha256");for(const[s,i]of[...e.workspaceFiles].sort(([e],[t])=>e.localeCompare(t)))t.update(s).update("\0").update(i).update("\0");const s=t.digest("hex"),i=`acp-artifact:sha256:${s}`;this.artifacts.set(i,e);const n=[...e.workspaceFiles.values()].reduce((e,t)=>e+t.byteLength,0);return{artifactRef:i,version:e.manifest?.version??"0.0.0",sha256:s,sizeBytes:String(n),source:"acp-verified"}}async readContent(e){const t=this.content.get(e);if(void 0===t)throw new Error(`unknown contentRef: ${e}`);return t}async readVerifiedArtifact(e){const t=this.artifacts.get(e.artifactRef);if(!t)throw new Error(`unknown verified artifact: ${e.artifactRef}`);return t}}export const lateBoundRuntimeAgentProjectionPort=e=>({prepareCommittedMutation:async t=>{const s=e();if(!s)throw new Error("agent projection production is unavailable");return await s.prepareCommittedMutation(t)},applyCommittedMutation:async t=>{const s=e();if(!s)throw new Error("agent projection production is unavailable");return await s.applyCommittedMutation(t)},compactCommittedMutation:async t=>{const s=e();if(!s)throw new Error("agent projection production is unavailable");await s.compactCommittedMutation(t)}});function deriveSessionId(e,t){const s=createHash("sha256").update(`${t}:${e}`).digest("hex").substring(0,32);return`session:${s.slice(0,8)}-${s.slice(8,12)}-${s.slice(12,16)}-${s.slice(16,20)}-${s.slice(20,32)}`}function parseAgentIdFromKey(e){const t=e.split(":");return(t.length>=3&&"agent"===t[0]?t[1]:"")||"main"}function createSessionObject(e){const t=parseAgentIdFromKey(e.gatewayKey);return{sessionId:deriveSessionId(e.gatewayKey,e.instanceId),gatewaySessionKey:e.gatewayKey,agentId:t,platformAgentId:e.resolvePlatformAgentId?.(t),createdAt:Date.now(),chatReplayDedupKeys:new Set,cwd:e.cwd,title:e.title,channelType:resolveChannelFromSessionKey(e.gatewayKey,e.thirdPartyChannels)}}class HistoryUpdateRecorder{transport;updates=[];constructor(e){this.transport=e}sendSessionUpdate(e,t,s){const i=this.transport.materializeSessionUpdate(t,s);return null!==i&&this.updates.push(i),!0}}export class AcpGatewayBridgeCore{state;transport;config;logger;writeObs;getGateway;thirdPartyChannels;beeosFileResolver;cloud;triggerAgentSync;sessionMutationCoordinator;runtimeCommands;runtimeAgentIdentities;runtimeLeases;runtimeCommandResources;localHistoryBoundary;activeRuns;markAgentProjectionDirty;projectionObservationTimeoutMs;cronChannelCache=new Map;static CRON_CHANNEL_CACHE_MAX=256;localSessionHistory;mcp;catalogPrepareInFlight=new Set;runtimeTerminalService;runOutputRouter;agentMappings;initialized=!1;get agentMapping(){return this.agentMappings.snapshot()}cronIsolatedSessionKeys=new Set;cronIdentities;cronReady;sessionPreloadInFlight=null;sessionPreloadInFlightGeneration=null;sessionPreloadReady=Promise.resolve();resolveSessionPreloadReady=null;rejectSessionPreloadReady=null;sessionPreloadGeneration=0;sessionPreloadBarrierActive=!1;sessionPreloadFailed=!1;async setAgentMapping(e){await this.agentMappings.replace(e)}async applyAgentMapping(e){for(const t of this.state.sessions.values())!t.platformAgentId&&t.agentId&&(t.platformAgentId=e.get(t.agentId)?.id);this.sessionPreloadFailed&&this.beginGatewaySessionPreload(),this.sessionPreloadBarrierActive&&await this.preloadSessionsFromGateway()}beginGatewaySessionPreload(){this.sessionPreloadBarrierActive&&this.sessionPreloadReady.catch(()=>{}),this.sessionPreloadGeneration+=1,this.sessionPreloadBarrierActive=!0,this.sessionPreloadFailed=!1,this.sessionPreloadReady=new Promise((e,t)=>{this.resolveSessionPreloadReady=e,this.rejectSessionPreloadReady=t}),this.sessionPreloadReady.catch(()=>{})}failGatewaySessionPreload(){this.sessionPreloadBarrierActive&&(this.sessionPreloadBarrierActive=!1,this.sessionPreloadFailed=!0,this.rejectSessionPreloadReady?.(new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway disconnected before session hydration completed")),this.resolveSessionPreloadReady=null,this.rejectSessionPreloadReady=null)}async waitForGatewaySessionPreload(){await this.sessionPreloadReady}onceAgentMappingReady(e){this.agentMappings.onceReady(e)}onAgentMappingUpdated(e){const t=this.agentMappings.snapshot();t&&t.size>0&&e(new Map(t)),this.agentMappings.subscribe(t=>e(new Map(t)))}markAgentSyncDone(){this.agentMappings.markSynchronized()}waitForAgentMapping(e=15e3){return this.agentMappings.waitForSynchronization(e)}handleTerminalRequest=null;handleTerminalNotification=null;onRunComplete=null;onRunFailed=null;onRunEnded=null;handleSlashCommand=null;onPromptStart=null;instanceId;getAuthoritativeInstanceId;mcpCredentialReferences;constructor(e){this.state=e.state,this.transport=e.transport,this.config=e.config,this.logger=e.logger,this.writeObs=e.writeObs,this.getGateway=e.getGateway,this.instanceId=e.instanceId||"unknown",this.getAuthoritativeInstanceId=e.getAuthoritativeInstanceId,this.mcpCredentialReferences=e.mcpCredentialReferences,this.thirdPartyChannels=e.thirdPartyChannels??new Set,this.beeosFileResolver=e.beeosFileResolver,this.cloud=e.cloud,this.triggerAgentSync=e.triggerAgentSync,this.markAgentProjectionDirty=e.markAgentProjectionDirty,this.projectionObservationTimeoutMs=e.projectionObservationTimeoutMs??5e3,this.sessionMutationCoordinator=e.sessionMutationCoordinator,this.agentMappings=e.agentMappings??new AgentMappingRegistry,this.agentMappings.subscribe(e=>this.applyAgentMapping(e)),this.runOutputRouter=e.runOutputRouter??new RunOutputRouter([new AcpRunOutputChannel({state:this.state,transport:this.transport})],{lifecycle:{terminal:(e,t)=>this.commitRunOutputTerminal(e,t)}}),this.runtimeAgentIdentities=e.runtimeAgentIdentities??new FileRuntimeAgentIdentityRegistry(join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","runtime-agent-identities.json")),this.runtimeLeases=e.runtimeLeases??new FileRuntimeLeasePort(join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","runtime-operation-leases.json")),this.runtimeCommandResources=e.runtimeCommandResources??new AcpRuntimeCommandResourceStore((t,s)=>e.cloud&&new URL(t).origin===e.cloud.baseUrl.origin?e.cloud.asFetch()(t,s):fetch(t,s),e.runtimeTemplatePackageParser?.parse??parseAndValidateTemplatePackage,e.runtimeTemplatePackageParser?.isValidationError??(e=>e instanceof TemplatePackageError));const t=e.runtimeCommandJournal??(e.runtimeLeases||process.env.VITEST?new InMemoryRuntimeCommandJournal:new FileRuntimeCommandJournal(join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","runtime-command-journal.json"),void 0,void 0,{onMaintenanceError:e=>this.logger.error?.(`[runtime-journal] event=checkpoint_failed trigger=threshold error=${e instanceof Error?e.message:String(e)}`)}));this.localSessionHistory=new LocalSessionHistory({logger:this.logger}),this.cronIdentities=e.cronIdentities??(process.env.VITEST?createInMemoryCronIdentityStore():createCronIdentityStore(this.logger)),this.cronReady=this.cronIdentities.initialize(),this.cronReady.catch(()=>{});const s=e.mcpCredentialReferences?new CredentialReferenceRuntimeResolver(e.mcpCredentialReferences):void 0,i=e.mcpProjection?new McpDesiredStateController(new McpDesiredStateStore(e.mcpDesiredStatePath??join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","mcp-desired-state.json")),e.mcpProjection,s,e.mcpCredentialReferences,t,e.runtimeReceiptHistory,this.logger):void 0;this.mcp=new McpService({transport:this.transport,logger:this.logger,state:this.state,...i?{desiredState:i}:{}}),this.localHistoryBoundary=new LocalConversationHistoryBoundary(e=>{const t=this.state.sessions.get(e);t&&(t.chatReplayDedupKeys.clear(),this.onSessionCleared?.(t.sessionId,t.gatewaySessionKey))}),this.activeRuns=e.activeRuns??new CompositeActiveRunPort;const n=new RuntimeCronService({getGateway:e.getGateway,identities:this.cronIdentities,channels:{allocate:async(e,t)=>await this.allocateCronChannel(e,t),lookup:e=>this.cronChannelCache.get(e),remember:(e,t)=>this.setCronChannelCache(e,t),forget:e=>{this.cronChannelCache.delete(e)}}}),o=new RuntimeSessionControlService({convergeRun:async({runtimeSessionKey:e})=>{const t=this.state.sessionsByGatewayKey.get(e),s=(new Date).toISOString();if(!t||t.promptDone&&void 0===t.pendingPromptId&&void 0===t.currentRunId)return{state:"no_active_run",quiescence:{state:"verified",observedAt:s}};const i=this.getGateway();if(!i?.isConnected)return{state:"recovery_required",reason:"gateway_disconnected"};if(!t.writeGateToken||!t.currentRunId)return{state:"recovery_required",reason:"run_conflict",runtimeRunId:t.currentRunId??"unknown"};const n=t.currentRunId,o=await this.sessionMutationCoordinator.requestCancel(t.writeGateToken,i);if(!o.ok)return{state:"recovery_required",reason:"terminal_not_observed",runtimeRunId:n};const r=await o.terminalObservation;return r.ok?{state:"terminal_observed",runtimeRunId:n,abortObservation:o.acknowledged?{state:"target_acknowledged",aborted:!0,runIds:[n],targetRunAcknowledged:!0}:{state:"already_stopped",aborted:!1,runIds:[]},terminalState:"cancelled",quiescence:{state:"verified",observedAt:(new Date).toISOString()}}:{state:"recovery_required",reason:"quiescence_failed"===r.recovery.reason?"quiescence_failed":"quiescence_timeout"===r.recovery.reason?"quiescence_timeout":"terminal_not_observed",runtimeRunId:n}},reset:async e=>{const t=this.getGateway();if(!t?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected");await t.sessionsReset(e)}},new OpenClawSessionMetadataService(new OpenClawGatewaySessionMetadataPort(e.getGateway,(e,t)=>{const s=this.state.sessionsByGatewayKey.get(e);if(!s)throw new RuntimeCommandError("INVALID_PARAMS",`setModel projection requires a live local session for key=${e}`);void 0===t.modelOverrideId?delete s.modelOverrideId:s.modelOverrideId=t.modelOverrideId,s.effectiveModelId=t.effectiveModelId,void 0===t.modelProvider?delete s.modelProvider:s.modelProvider=t.modelProvider},{resolveLiveKey:e=>resolveLiveOpenClawSessionKey(e,this.state.sessions.values()),waitForHydration:async()=>await this.waitForGatewaySessionPreload(),reconcileMissingLiveKey:async(e,t)=>await this.reconcileMissingGatewaySession(e,t),materializeMissingModelKey:async(e,t)=>await this.materializeMissingModelSessionKey(e,t),registerMaterializedModelKey:e=>this.registerMaterializedModelSession(e)},this.sessionMutationCoordinator)),this.activeRuns),r=e.getAgentProjection?lateBoundRuntimeAgentProjectionPort(e.getAgentProjection):void 0,a=e.runtimeCommandDispatcher??new OpenClawRuntimeCommandDispatcher({getGateway:e.getGateway,resources:this.runtimeCommandResources,identities:this.runtimeAgentIdentities,leases:this.runtimeLeases,journal:t,logger:e.logger,...r?{projection:r}:{},...e.templateOriginReporter?{templateOriginReporter:e.templateOriginReporter}:{},...e.runExclusive?{runExclusive:e.runExclusive}:{}});this.runtimeCommands=a,a instanceof OpenClawRuntimeCommandDispatcher&&a.setCapabilities(new OpenClawRuntimeCapabilities({getGateway:e.getGateway,identities:this.runtimeAgentIdentities,sessions:o,skills:new RuntimeSkillsService({getGateway:e.getGateway,resources:this.runtimeCommandResources,getOpenclawHome:()=>this.getOpenclawHome()}),mcp:this.mcp,terminal:{execute:async(e,t)=>{if(!this.runtimeTerminalService)throw new RuntimeCommandError("INVALID_PARAMS","terminal canonical delegate is not configured");return await this.runtimeTerminalService.execute(e,t)}},canvas:new RuntimeCanvasService({apply:async(e,t)=>{await this.applyCanonicalCanvasControl(e,t)}}),cron:n})),this.transport.setSessionUpdateObserver((e,t,s)=>{const i=s?.turnId;if(!i)return;const n=this.state.sessions.get(e);n&&(recordTurnIdForRole(n,t,i),recordToolCallTurnForUpdate(n,t,i))})}setRuntimeTerminalService(e){this.runtimeTerminalService=e}setRuntimeReceiptHistoryPort(e){this.mcp.setRuntimeReceiptHistoryPort(e)}getRuntimeCommandDispatcher(){return this.runtimeCommands}async startMcp(){await this.mcp.start()}async stopMcp(){await this.mcp.stop()}nudgeMcpNativeProjection(){this.mcp.nudgeNativeProjection()}observeActiveRun(e){const t=e.currentRunId,s=e.writeGateToken,i=this.sessionMutationCoordinator.snapshot(e.gatewaySessionKey),n=void 0===e.pendingPromptId?e.gatewayRequestId??t:String(e.pendingPromptId);if(!t||!i||!n)return;const o={runtimeConversationUid:e.sessionId,runtimeSessionKey:e.gatewaySessionKey,historyGeneration:this.localHistoryBoundary.currentGeneration(e.sessionId),targetMessageId:n,runtimeRunId:t,activeRunRegistrationId:`acp:${i.operationId}`};return this.activeRuns.register(o,async()=>{const n=this.getGateway();if(!n?.isConnected||e.currentRunId!==t)return"recovery_required";const o=s?.operationId===i.operationId?await this.sessionMutationCoordinator.requestCancel(s,n):await this.sessionMutationCoordinator.requestCancelForSession(e.gatewaySessionKey,n);return o.ok?"accepted":!("reason"in o)||"terminal_already_observed"!==o.reason&&"no_active_mutation"!==o.reason?"recovery_required":"already_terminal"}),e.activeRunBinding=o,o}getActiveRunAuthority(){return{activeRuns:this.activeRuns,historyGeneration:e=>this.localHistoryBoundary.currentGeneration(e)}}resolvePlatformAgentId(e){return this.agentMapping?.get(e)?.id}resolveLocalAgentId(e){if(this.agentMapping&&e)for(const[t,s]of this.agentMapping)if(s.id===e)return t}getDefaultAgentId(){return this.agentMapping&&this.agentMapping.size>0?this.agentMapping.keys().next().value??"main":"main"}registerSession(e,t={}){const{ensureVerbose:s=!0}=t,i=(this.state.sessions.has(e.sessionId),this.state.sessionsByGatewayKey.get(e.gatewaySessionKey));!0===i?.canvasActive&&(e.canvasActive=!0),this.state.sessions.set(e.sessionId,e),this.state.sessionsByGatewayKey.set(e.gatewaySessionKey,e),s&&this.ensureVerboseFull(e),this.subscribeSessionMessages(e)}async ensureVerboseFull(e){if(e.verbosePatched)return;const t=this.getGateway();if(t?.isConnected){e.verbosePatched=!0;try{await t.sessionsPatch(e.gatewaySessionKey,{verboseLevel:"full"}),await this.scheduleSessionToolCatalog(e.agentId,e.gatewaySessionKey)}catch(t){e.verbosePatched=!1,this.logger.debug?.(`[bridge-core] ensureVerboseFull failed: ${t instanceof Error?t.message:t}`)}}}async resolveNativePromptSessionKey(e,t){const s=retainedVerifiedNativePrompt(e);if(!s||!this.runtimeAgentIdentities.resolveActiveLocalKey)throw new RuntimeCommandError("INVALID_PARAMS","native prompt target authority unavailable");t();const i=await this.runtimeAgentIdentities.resolveActiveLocalKey(s.runtimeAgentUid);if(t(),this.resolveLocalAgentId(s.params.platformAgentId)!==i)throw new RuntimeCommandError("INVALID_PARAMS","native prompt target does not match platform projection");const n=`agent:${i}:beeos-conversation:ctx-${s.command.context.conversation.runtimeConversationUid}`,o=this.state.findSessionByKey(n);if(o&&(o.agentId!==i||void 0!==o.platformAgentId&&o.platformAgentId!==s.params.platformAgentId))throw new RuntimeCommandError("INVALID_PARAMS","native prompt session belongs to another target");return n}async prepareNativePromptModel(e,t,s){const i=retainedVerifiedNativePrompt(e);if(!i)throw new RuntimeCommandError("INVALID_PARAMS","native model proof unavailable");const n=async()=>{s();const i=await this.resolveNativePromptSessionKey(e,s);s();const n=this.sessionMutationCoordinator.snapshot(i);if(i!==t.sessionKey||n?.operationId!==t.operationId)throw new RuntimeCommandError("OUTCOME_UNKNOWN","native model preparation lost its physical session gate")};await n();const o=this.getGateway();if(!o?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected");const r=await o.sessionsList();await n();const a=i.command.context.conversation.runtimeConversationUid,l=(e,s)=>{const i=[...openClawSessionKeysFromList(e)].filter(e=>openClawKeyMatchesConversation(e,a));if(i.some(e=>e!==t.sessionKey)||s&&!i.includes(t.sessionKey))throw new RuntimeCommandError("OUTCOME_UNKNOWN","native model session conflicts with authoritative target")};l(r,!1);const d=new OpenClawGatewaySessionMetadataPort(()=>this.getGateway()),c=await d.prepareNativeModel(t.sessionKey,i.params.modelOverrideId,n,i.params.mode);await n();const m=await o.sessionsList();await n(),l(m,!0),this.registerMaterializedModelSession(t.sessionKey);const u=this.state.findSessionByKey(t.sessionKey);void 0===c.modelOverrideId?delete u.modelOverrideId:u.modelOverrideId=c.modelOverrideId,u.effectiveModelId=c.effectiveModelId,void 0===c.modelProvider?delete u.modelProvider:u.modelProvider=c.modelProvider,u.channelId=i.command.context.conversation.runtimeConversationUid,u.inboundMessageId=i.params.taskRequestMessageId,u.activeMentions=void 0,this.state.lastActiveSessionId=u.sessionId,this.onSessionLoaded?.(u.sessionId,u.gatewaySessionKey,i.params.platformAgentId),await this.ensureVerboseFull(u),this.onPromptStart?.()}async applyImTurnModel(e,t){const s=new OpenClawGatewaySessionMetadataPort(()=>this.getGateway());await s.prepareNativeModel(e,t,async()=>{})}async restoreImTurnModel(e){const t=new OpenClawGatewaySessionMetadataPort(()=>this.getGateway());await t.prepareNativeModel(e,void 0,async()=>{})}ensureSessionForKey(e){const t=this.state.findSessionByKey(e);if(t)return t;const s=createSessionObject({gatewayKey:e,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels});return this.registerSession(s),this.logger.debug?.(`[bridge-core] ensureSessionForKey: lazily created session sid=${s.sessionId} key=${e}`),this.onSessionLoaded?.(s.sessionId,s.gatewaySessionKey,s.platformAgentId),s}async preloadSessionsFromGateway(){const e=this.sessionPreloadGeneration;if(this.sessionPreloadInFlight&&this.sessionPreloadInFlightGeneration===e)return await this.sessionPreloadInFlight;const t=this.preloadGatewaySessionIndex(e);this.sessionPreloadInFlight=t,this.sessionPreloadInFlightGeneration=e;try{await t,e===this.sessionPreloadGeneration&&this.sessionPreloadBarrierActive&&(this.sessionPreloadBarrierActive=!1,this.resolveSessionPreloadReady?.(),this.resolveSessionPreloadReady=null,this.rejectSessionPreloadReady=null)}catch(t){throw e===this.sessionPreloadGeneration&&this.sessionPreloadBarrierActive&&(this.sessionPreloadBarrierActive=!1,this.sessionPreloadFailed=!0,this.rejectSessionPreloadReady?.(t instanceof Error?t:new Error("session preload failed")),this.resolveSessionPreloadReady=null,this.rejectSessionPreloadReady=null),t}finally{this.sessionPreloadInFlight===t&&(this.sessionPreloadInFlight=null,this.sessionPreloadInFlightGeneration=null)}}async preloadGatewaySessionIndex(e){const t=this.getGateway();if(this.logSessionAuthority("session_preload_start",{generation:e,reason:t?.isConnected?"gateway_connected":"gateway_not_connected"}),!t?.isConnected)throw this.logSessionAuthority("session_preload_failure",{generation:e,reason:"gateway_not_connected"}),new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected for session hydration");try{const s=await t.sessionsList(),i=this.mergeGatewaySessions(s);this.logSessionAuthority("session_preload_success",{generation:e,loaded:i,reason:"authoritative_sessions_list"})}catch(t){throw this.logSessionAuthority("session_preload_failure",{generation:e,reason:t instanceof RuntimeCommandError?t.code:"sessions_list_failed"}),t}}mergeGatewaySessions(e,t){if(!e||"object"!=typeof e)return 0;const s=e.sessions;if(!Array.isArray(s))return 0;let i=0;for(const e of s){if(!e||"object"!=typeof e||Array.isArray(e))continue;const s=e,n=s.sessionKey??s.key??s.id??s.name;if("string"!=typeof n||!n.trim()||n.trim()!==n||t&&!t(n)||this.state.findSessionByKey(n))continue;const o=s.label||s.derivedTitle||s.title||void 0,r=o&&!isPlaceholderTitle(o)?o:void 0,a=createSessionObject({gatewayKey:n,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,title:r});this.registerSession(a,{ensureVerbose:!1}),i+=1}return i}async reconcileMissingGatewaySession(e,t){await this.assertSessionReconcileFence(t);const s=this.getGateway();if(!s?.isConnected)throw this.logSessionAuthority("session_reconcile_decision",{reason:"gateway_not_connected",runtimeSessionKeyHash:this.sessionKeyHash(e)}),new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected for session reconciliation");const i=await s.sessionsList();await this.assertSessionReconcileFence(t);const n=!e.startsWith("agent:"),o=[...openClawSessionKeysFromList(i)].filter(t=>t===e?!n:openClawKeyMatchesConversation(t,e));if(this.logSessionAuthority("session_reconcile_decision",{reason:o.length?"exact_candidates":"no_exact_candidate",runtimeSessionKeyHash:this.sessionKeyHash(e),candidateHashes:o.map(e=>this.sessionKeyHash(e))}),!o.length)return;const r=new Set(o),a=this.mergeGatewaySessions(i,e=>r.has(e));this.logSessionAuthority("session_reconcile_decision",{reason:"reconciled_exact_candidates",runtimeSessionKeyHash:this.sessionKeyHash(e),loaded:a,candidateHashes:o.map(e=>this.sessionKeyHash(e))})}async materializeMissingModelSessionKey(e,t){await this.assertSessionReconcileFence(t);const s=t.attempt,i=s.verifiedMessageGrant?.claims,n=i?.command;if("service"!==s.origin||!i||"conversation"!==i.scope||"session/set_model"!==i.method||"session.setModel"!==n?.name||"conversation"!==n.context.scope||i.runtimeSessionKey!==e||i.runtimeConversationUid!==e||n.context.conversation.runtimeSessionKey!==e||n.context.conversation.runtimeConversationUid!==i.runtimeConversationUid)throw this.logSessionAuthority("model_intent_materialization_rejected",{reason:"verified_service_target_required",runtimeSessionKeyHash:this.sessionKeyHash(e)}),new RuntimeCommandError("INVALID_PARAMS","model-intent session materialization requires an exact verified Service target");if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e))throw new RuntimeCommandError("INVALID_PARAMS","model-intent session materialization requires a canonical conversation UUID");let o;try{if(!this.runtimeAgentIdentities.resolveActiveLocalKey)throw new Error("identity resolver unavailable");o=await this.runtimeAgentIdentities.resolveActiveLocalKey(i.runtimeAgentUid)}catch{throw new RuntimeCommandError("INVALID_PARAMS","verified model-intent runtime agent is not active")}if(this.resolveLocalAgentId(i.platformAgentId)!==o)throw new RuntimeCommandError("INVALID_PARAMS","verified model-intent agent does not match the active platform projection");const r=`agent:${o}:beeos-conversation:ctx-${e}`,a=this.getGateway();if(!a?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected for model-intent session materialization");const l=await a.sessionsList();await this.assertSessionReconcileFence(t);const d=[...openClawSessionKeysFromList(l)].filter(t=>openClawKeyMatchesConversation(t,e));if(d.length){if(1===d.length&&d[0]===r)return this.mergeGatewaySessions(l,e=>e===r),r;throw new RuntimeCommandError("INVALID_PARAMS","verified model-intent target conflicts with an existing OpenClaw session")}return this.logSessionAuthority("model_intent_materialization_allowed",{runtimeSessionKeyHash:this.sessionKeyHash(e),openClawSessionKeyHash:this.sessionKeyHash(r)}),r}registerMaterializedModelSession(e){this.state.findSessionByKey(e)||(this.registerSession(createSessionObject({gatewayKey:e,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels}),{ensureVerbose:!1}),this.logSessionAuthority("model_intent_materialization_committed",{openClawSessionKeyHash:this.sessionKeyHash(e)}))}async assertSessionReconcileFence(e){if(e.lease.operationId!==e.attempt.operationId||e.lease.runtimeEpoch!==e.attempt.runtimeEpoch||e.attempt.abortSignal?.aborted)throw this.logSessionAuthority("session_reconcile_decision",{reason:"operation_fence_mismatch"}),new RuntimeCommandError("INVALID_PARAMS","session reconciliation is fenced");try{await this.runtimeLeases.renew(e.lease)}catch{throw this.logSessionAuthority("session_reconcile_decision",{reason:"lease_epoch_mismatch"}),new RuntimeCommandError("INVALID_PARAMS","session reconciliation is fenced")}if(e.attempt.abortSignal?.aborted)throw this.logSessionAuthority("session_reconcile_decision",{reason:"operation_aborted"}),new RuntimeCommandError("INVALID_PARAMS","session reconciliation is fenced")}sessionKeyHash(e){return createHash("sha256").update(e).digest("hex").slice(0,16)}logSessionAuthority(e,t){this.logger.info?.(`[bridge-core] ${JSON.stringify({event:e,...t})}`)}async handleRequest(e){this.writeObs({component:"bridge-core",domain:"forward",name:`rpc.request.${e.method}`,severity:"debug",payload:{id:e.id,method:e.method}});const t=e;try{switch(t.method){case"initialize":await this.handleInitialize(t);break;case"ping":this.transport.sendResult(t.id,{pong:!0});break;case"session/new":await this.handleSessionNew(t);break;case"session/load":await this.handleSessionLoad(t);break;case"session/list":await this.handleSessionList(t);break;case"session/rename":await this.handleSessionRename(t);break;case"session/delete":await this.handleSessionDelete(t);break;case"session/clear":await this.handleSessionClear(t);break;case"session/prompt":await this.handleSessionPrompt(t);break;case"session/set_mode":await this.handleSessionSetMode(t);break;case"session/set_model":await this.handleSessionSetModel(t);break;case"agent/create":await this.handleAgentCreate(t);break;case"agent/update":await this.handleAgentUpdate(t);break;case"agent/delete":await this.handleAgentDelete(t);break;case"agent/applyTemplate":await this.handleApplyTemplate(t);break;case"skills/list":await this.handleSkillsList(t);break;case"skills/install":await this.handleSkillsInstall(t);break;case"skills/uninstall":await this.handleSkillsUninstall(t);break;case"skills/update":await this.handleSkillsUpdate(t);break;case"models/list":await this.handleModelsList(t);break;case"session/cancel":await this.handleSessionCancel(t.id,t.params);break;case"cron/list":case"cron/status":case"cron/add":case"cron/update":case"cron/remove":case"cron/run":case"cron/runs":await this.handleCronRequest(t);break;default:"agent/list"===e.method?await this.handleAgentList(e):"_beeos/session/history"===e.method?await this.handleSessionHistoryPage(e):"mcp/list"===e.method?await this.handleMcpList(e):"mcp/set"===e.method?await this.handleMcpSet(e):"mcp/unset"===e.method?await this.handleMcpUnset(e):e.method.startsWith("terminal/")&&this.handleTerminalRequest?await this.handleTerminalRequest(e):this.transport.sendAcpError(e.id,Errors.MethodNotFound,{message:`Method not found: ${e.method}`})}}catch(t){this.transport.sendAcpError(e.id,Errors.InternalError,{message:t instanceof Error?t.message:"Internal error"})}}handleCanvasNotification=null;getCanvasState=null;onSessionLoaded=null;onSkillInstalled=null;onSessionDeleted=null;onSessionCleared=null;handleNotification(e){switch(e.method){case"session/cancel":this.observeLifecycle(this.handleSessionCancel(void 0,e.params),"session_cancel_notification");break;case"canvas/toggle":case"canvas/clear":case"canvas/reference":case"canvas/dimensions":this.observeLifecycle(this.dispatchLegacyCanvasNotification(e.method,e.params),`canvas_notification_${e.method.replace("/","_")}`);break;default:e.method.startsWith("terminal/")&&this.handleTerminalNotification?this.handleTerminalNotification(e.method,e.params):this.logger.debug?.(`[bridge-core] Unknown notification: ${e.method}`)}}async handleInitialize(e){await this.cronReady,this.initialized=!0;const t=this.transport.forwardControl;!1===e.params?.forwardThinking&&(t.forwardThinking=!1),!1===e.params?.forwardToolCalls&&(t.forwardToolCalls=!1),this.transport.sendResult(e.id,{protocolVersion:ACP_PROTOCOL_VERSION,agentCapabilities:{loadSession:!0,skills:!0,multipleSessions:!0,sessionCapabilities:{"web-ssh":!!this.config.bridge.shell.enabled,canvas:!!this.config.canvasEnabled},promptCapabilities:{image:!0,audio:!1,embeddedContext:!0},mcp:{}},agentInfo:{name:"beeos-claw",title:"BeeOS Agent Bridge",version:"0.1.0"},_meta:{},authMethods:[]})}async createRuntimeSession(e,t){const s=e.operationId.trim(),i=e.platformAgentId.trim();if(!s)throw new Error("session/new operationId is required");if(assertTrustedCoreExecutionAttempt(t,{operationId:s,runtimeEpoch:t.runtimeEpoch}),"service"!==t.origin)throw new Error("session/new runtime allocation requires Service authority");const n=this.resolveLocalAgentId(i);if(!i||!n)throw new RuntimeSessionNewRejectedError("session/new target agent is unknown");const o=`agent:${n}:service:${s}`,r=this.allocateSession({agentId:n,gatewaySessionKey:o,cwd:e.cwd??".",...void 0===e.title?{}:{title:e.title}});return await this.prepareSessionToolCatalog(n,o),{sessionId:r.sessionId,gatewaySessionKey:o,agentId:n,platformAgentId:i}}async handleSessionNew(e){const t="string"==typeof e.params?.agentId&&e.params.agentId||this.config.gateway.agentId||this.getDefaultAgentId(),s=`agent:${t}:${randomUUID()}`,i="string"==typeof e.params?.cwd?e.params.cwd:".",n=this.allocateSession({agentId:t,gatewaySessionKey:s,cwd:i});await this.prepareSessionToolCatalog(t,s),this.transport.sendResult(e.id,{sessionId:n.sessionId,modes:{availableModes:[{...DEFAULT_MODE}],currentModeId:DEFAULT_MODE.id},_meta:{sessionKey:s,agentId:n.agentId,platformAgentId:n.platformAgentId??null}}),this.fetchHistoryInBackground(s)}allocateSession(e){const t=this.state.findSessionByKey(e.gatewaySessionKey);if(t)return t;const s=createSessionObject({gatewayKey:e.gatewaySessionKey,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,...void 0===e.cwd?{}:{cwd:e.cwd},...void 0===e.title?{}:{title:e.title}});if(s.agentId!==e.agentId)throw new Error("session/new gateway key agent mismatch");return this.registerSession(s,{ensureVerbose:!1}),s}prepareWorkspaceCatalog(){this.scheduleCatalogPrepare({agentId:this.getDefaultAgentId(),mode:"catalog",keyKind:"boot"})}scheduleSessionToolCatalog(e,t){const s=classifyCatalogSessionKey(t);return this.runCatalogPrepare({agentId:e,sessionKey:t,mode:"im_conversation"===s?"effective":"both",keyKind:s})}scheduleCatalogPrepare(e){this.runCatalogPrepare(e)}async prepareSessionToolCatalog(e,t){await this.runCatalogPrepare({agentId:e,sessionKey:t,mode:"both",keyKind:classifyCatalogSessionKey(t)})}async runCatalogPrepare(e){const t=this.getGateway();if(!t?.isConnected||"function"!=typeof t.request)return;const s=`${e.mode}:${e.agentId}:${e.sessionKey??""}`;if(!this.catalogPrepareInFlight.has(s)){this.catalogPrepareInFlight.add(s);try{await prepareOpenClawToolCatalog({request:(e,s)=>t.request(e,s),agentId:e.agentId,...void 0===e.sessionKey?{}:{sessionKey:e.sessionKey},mode:e.mode,keyKind:e.keyKind,logger:this.logger})}finally{this.catalogPrepareInFlight.delete(s)}}}fetchHistoryInBackground(e){const t=this.getGateway();t?.isConnected&&loadHistory(t,e,50).catch(e=>{this.logger.debug?.(`[bridge-core] Background history fetch failed: ${e instanceof Error?e.message:e}`)})}async subscribeSessionMessages(e){if(e.messageSubscriptionActive)return;const t=this.getGateway();if(t?.isConnected)try{await t.sessionsMessagesSubscribe(e.gatewaySessionKey),e.messageSubscriptionActive=!0,this.logger.debug?.(`[bridge-core] session.message subscribed sessionKey=${e.gatewaySessionKey}`)}catch(t){this.logger.debug?.(`[bridge-core] session.message subscribe failed key=${e.gatewaySessionKey}: ${t instanceof Error?t.message:t}`)}}async resubscribeAllSessionMessages(){for(const e of this.state.sessions.values())e.messageSubscriptionActive=!1,await this.subscribeSessionMessages(e)}handleSessionMessage(e){const t=e?.sessionKey,s=e?.messageId;if(!t||!s)return;const i=this.state.sessionsByGatewayKey.get(t);if(!i)return;const n=e.message?.role;if("toolResult"===n||"tool"===n)return void this.handlePersistedToolResultMessage(i,e).catch(e=>{this.logger.warn?.(`[bridge-core] persisted toolResult handling failed sessionKey=${t} messageId=${s}: ${e instanceof Error?e.message:String(e)}`)});const o="user"===n?"user":"system"===n?"system":"assistant",r=i.lastTurnIdByRole?.[o];if(!r)return void this.logger.debug?.(`[bridge-core] session.message skipped: no lastTurnId role=${o} sessionKey=${t} messageId=${s}`);const a=e.message?.timestamp,l="number"==typeof a&&a>0?new Date(a).toISOString():void 0,d={turnId:r};"number"==typeof e.messageSeq&&(d.seq=e.messageSeq),this.transport.sendSessionUpdate(i.sessionId,{sessionUpdate:"message_id_bind",role:o,messageId:s,...l?{timestamp:l}:{}},d),i.lastTurnIdByRole&&(i.lastTurnIdByRole[o]=void 0),"assistant"===o&&reconcileMissingArtifacts({localHistory:this.localSessionHistory,runOutputRouter:this.runOutputRouter,config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:()=>this.getCanvasState?.()??null,isSessionAlive:e=>this.state.sessions.has(e)},{session:i,turnId:r,messageId:s,runId:r})}async handlePersistedToolResultMessage(e,t){const s=t.messageId;if(!s)return;const i=extractPersistedToolResultEnvelope(t.message),n=this.extractPersistedToolResultFromHistory(e,s),o=mergePersistedToolResultEnvelopes(n,i);if(!o?.toolCallId)return void this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.skipped_no_tool_call_id",severity:"warn",payload:{sessionId:e.sessionId,messageId:s}});n&&this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.hydrated_from_history",severity:"debug",payload:{sessionId:e.sessionId,messageId:s,toolCallId:o.toolCallId,toolName:o.toolName}});const r=e.toolCallTurns?.get(o.toolCallId),a=r?.turnId??resolveTurnId(e);if(!a)return void this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.skipped_no_turn",severity:"warn",payload:{sessionId:e.sessionId,messageId:t.messageId,toolCallId:o.toolCallId,toolName:o.toolName}});const l=projectPersistedToolResultForReplay({toolCallId:o.toolCallId,toolName:o.toolName||r?.toolName||"tool",...o.rawText?{rawText:o.rawText}:{},...o.rawResult?{rawResult:o.rawResult}:{},...o.timestamp?{timestamp:o.timestamp}:{}});await emitToolResultProjectionForTurn({session:e,projection:l,toolCallId:o.toolCallId,turnId:a,config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:()=>this.getCanvasState?.()??null,sendUpdate:s=>(a&&void 0===s._turnId&&(s._turnId=a),this.transport.sendSessionUpdate(e.sessionId,s,{turnId:a,..."number"==typeof t.messageSeq?{seq:t.messageSeq}:{}}))})}extractPersistedToolResultFromHistory(e,t){if(!e.agentId)return;const s=this.localSessionHistory.readEntries({agentId:e.agentId,sessionKey:e.gatewaySessionKey,sessionId:e.sessionId});return findPersistedToolResultEnvelopeByMessageId(s,t)}async recoverSessionFromGateway(e){const t=this.getGateway();if(t?.isConnected)try{const s=await t.sessionsList();if(!s||"object"!=typeof s)return;const i=s.sessions;if(!Array.isArray(i))return;for(const t of i){const s=t,i=s.sessionKey??s.key??s.id??s.name;if(i&&"string"==typeof i&&deriveSessionId(i,this.instanceId)===e){const e=s.label||s.derivedTitle||s.title||void 0,t=e&&!isPlaceholderTitle(e)?e:void 0,n=createSessionObject({gatewayKey:i,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,title:t});return this.registerSession(n),n}}}catch(e){this.logger.debug?.("[bridge-core] recoverSessionFromGateway failed:",e)}}async resolveOrCreateSession(e){let t=this.state.sessions.get(e);if(!t&&e.startsWith("agent:")&&(t=this.state.findSessionByKey(e)),t||e.startsWith("agent:")||(t=await this.recoverSessionFromGateway(e)),t)return{session:t,created:!1};const s=e.startsWith("agent:"),i=this.config.gateway.agentId||this.getDefaultAgentId();return t=createSessionObject({gatewayKey:s?e:`agent:${i}:${e}`,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels}),this.registerSession(t),{session:t,created:!0}}async handleSessionLoad(e){const t=e.params?.sessionId;if(!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const{session:s}=await this.resolveOrCreateSession(t),i=s.sessionId;this.state.lastActiveSessionId=i,s.chatReplayDedupKeys.clear(),s.sessionLoadInProgress=!0;try{const e=await this.emitHistoryTurns(s);this.emitCurrentStreamSnapshot(s,e)}finally{s.sessionLoadInProgress=!1}this.transport.sendResult(e.id,{sessionId:i}),this.onSessionLoaded?.(i,s.gatewaySessionKey,s.platformAgentId),this.pushSkillsList().catch(e=>{this.logger.warn?.(`[bridge-core] pushSkillsList failed: ${e instanceof Error?e.message:e}`)})}async emitHistoryTurns(e){const t=new Set,s=e.sessionId;try{const i=this.getGateway(),n=e.assistantMq&&!e.assistantMq.closed,o=n?null:i,r=await loadHistory(o,e.gatewaySessionKey,100,{localHistory:this.localSessionHistory,agentId:e.agentId,sessionId:e.sessionId,logger:this.logger,emitObs:e=>{this.writeObs({component:"history-replay",domain:"lifecycle",name:e.name,severity:e.severity,payload:e.payload})}}),a=groupIntoTurns(r),l=a.filter(e=>"user"===e.role).length,d=a.filter(e=>"assistant"===e.role).length,c=a.filter(e=>e.toolCalls?.length).length,m=a.reduce((e,t)=>e+(t.toolCalls?.length??0),0);if(this.logger.debug?.(`[bridge-core] handleSessionLoad sid=${s} messages=${r.length} turns=${a.length} userTurns=${l} asstTurns=${d} turnsWithToolCalls=${c} totalToolCalls=${m}`),l>0&&e.pendingUserTurnUpdates&&e.pendingUserTurnUpdates.length>0&&(e.pendingUserTurnUpdates=[]),a.length>0&&isPlaceholderTitle(e.title)&&"manual"!==e.titleSource){const t=a.find(e=>{if("user"!==e.role)return!1;if("internal_system"===e.provenance?.kind)return!1;if(isCronPromptText(e.content))return!1;const t=cleanHistoryUserContent(e.content);return t.length>0&&!/^Sender\s*\(untrusted/i.test(t)});if(t?.content){const i=cleanHistoryUserContent(t.content).slice(0,64).replace(/\n/g," ").trim();if(i){e.title=i,e.titleSource="truncated";const t=this.getGateway();t?.isConnected&&t.sessionsPatch(e.gatewaySessionKey,{label:i}).catch(()=>{}),this.transport.sendSessionUpdate(s,{sessionUpdate:"title_update",title:i}),this.tryGenerateTitleFromHistory(e,s,a)}}}const u=t=>`history:${e.gatewaySessionKey}:${t}`;let p=-1;if(n)for(let e=a.length-1;e>=0;e--)if("assistant"===a[e].role){p=e;break}const{replayedToolCallIds:h,streamCompleteCount:g}=await this.replayHistoryTurnsToSink(e,a,this.transport,{mqActive:Boolean(n),lastAsstIdx:p,turnIdFor:(e,t)=>u(t)});for(const e of h)t.add(e);this.logger.debug?.(`[bridge-core] emitHistoryTurns DONE sid=${s} historyStreamCompleteEmitted=${g} replayedToolCallIds=${t.size}`)}catch(e){this.logger.warn?.("[bridge-core] Failed to load session history:",e)}return t}async replayHistoryTurnsToSink(e,t,s,i){const n=e.sessionId,o=new Set;let r=0;for(let a=0;a<t.length;a++){const l=t[a],d=i.turnIdFor(l,a);if(i.mqActive&&a===i.lastAsstIdx){const e=classifyHistoricalAssistantTurnCompletion(l,{mqActive:i.mqActive,isSkippedForLiveMq:!0});this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] assistant — ${"live"===e.kind?e.reason:"mq active"}, deferring to emitCurrentStreamSnapshot`);continue}if("user"===l.role&&"internal_system"===l.provenance?.kind){this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] internal_system user message`);continue}if("user"===l.role&&/^\s*system\s*\(untrusted[^)]*\)/i.test(l.content)){this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] system-injected user message`);continue}if("user"===l.role&&isHeartbeatPromptText(l.content)){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] heartbeat cron prompt sessionId=${e.sessionId} contentLen=${l.content.length} preview="${previewSanitizedText(l.content)}"`);continue}if("user"===l.role&&isCronPromptText(l.content)){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] cron-triggered user message sessionId=${e.sessionId} contentLen=${l.content.length} preview="${previewSanitizedText(l.content)}"`);continue}if("assistant"===l.role){const t=classifyHeartbeatFilter(l.content);if(t.filtered){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] heartbeat assistant reply reason=${t.reason} sessionId=${e.sessionId} contentLen=${l.content.length} preview="${previewSanitizedText(l.content)}"`);continue}}const c="assistant"===l.role?classifyHistoricalAssistantTurnCompletion(l,{mqActive:i.mqActive,isSkippedForLiveMq:!1}):void 0;if("error"===c?.kind||"interrupted"===c?.kind&&c.failure)s.sendSessionUpdate(n,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:""},...l.timestamp?{timestamp:l.timestamp}:{}},{turnId:d,...void 0!==l._seq?{seq:l._seq}:{}}),s.sendSessionUpdate(n,{sessionUpdate:"stream_complete",stopReason:c.stopReason,error:c.message,errorCode:c.errorCode},{turnId:d}),this.logger.info?.(`[bridge-core] history_runtime_failure_terminalized session_id=${n} turn_id=${d} error_code=${c.errorCode} stop_reason=${c.stopReason} source=history_replay`),r+=1;else if("system"!==l.role)if(this.logger.debug?.(`[bridge-core] SENDING history_turn[${a}] role=${l.role} contentLen=${l.content.length} content=${JSON.stringify(l.content.slice(0,80))} tcCount=${l.toolCalls?.length??0}`),"user"===l.role)this.replayUserTurnAsChunks(s,n,l.content,l.metadata,l.timestamp,d,l._seq);else{if(await this.replayAssistantTurnAsStream(s,n,l,d),l.toolCalls)for(const e of l.toolCalls)e.id&&o.add(e.id);const e=c??classifyHistoricalAssistantTurnCompletion(l,{mqActive:i.mqActive,isSkippedForLiveMq:!1});if("interrupted"===e.kind){const t=new Set(e.incompleteToolCallIds);for(const i of l.toolCalls??[])t.has(i.id)&&s.sendSessionUpdate(n,buildToolCallUpdatePayload({toolCallId:i.id,toolName:i.name,outputText:e.message,kind:i.kind,status:"failed",timestamp:l.timestamp}),{turnId:d});this.logger.info?.(`[bridge-core] history_orphan_turn_terminalized session_id=${n} turn_id=${d} tool_call_ids=${e.incompleteToolCallIds.join(",")} stop_reason=${e.stopReason} source=history_replay`),s.sendSessionUpdate(n,{sessionUpdate:"stream_complete",stopReason:e.stopReason,error:e.message,errorCode:e.errorCode},{turnId:d}),r+=1;continue}if("completed"!==e.kind)continue;s.sendSessionUpdate(n,{sessionUpdate:"stream_complete",stopReason:e.stopReason},{turnId:d}),r+=1}else this.logger.debug?.(`[bridge-core] SENDING system_message[${a}] contentLen=${l.content.length}`),s.sendSessionUpdate(n,{sessionUpdate:"system_message_chunk",content:{type:"text",text:l.content},metadata:l.metadata,...l.timestamp?{timestamp:l.timestamp}:{}},{turnId:d,...void 0!==l._seq?{seq:l._seq}:{}})}return{replayedToolCallIds:o,streamCompleteCount:r}}async handleSessionHistoryPage(e){const t=e.params??{},s="string"==typeof t.sessionId?t.sessionId:"";if(!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const i="string"==typeof t.cursor&&t.cursor.length>0?t.cursor:void 0,n="number"==typeof t.limit&&Number.isFinite(t.limit)&&t.limit>0?Math.min(1e3,Math.floor(t.limit)):100,{session:o}=await this.resolveOrCreateSession(s),r=this.getGateway(),a=await loadHistoryPage(r,o.gatewaySessionKey,{...i?{cursor:i}:{},limit:n,logger:this.logger}),l=groupIntoTurns(a.messages),d=new HistoryUpdateRecorder(this.transport);await this.replayHistoryTurnsToSink(o,l,d,{mqActive:!1,lastAsstIdx:-1,turnIdFor:(e,t)=>void 0!==e._seq?`history:${o.gatewaySessionKey}:seq:${e._seq}`:`history:${o.gatewaySessionKey}:seq:none:${i??"head"}:${t}`}),this.logger.debug?.(`[bridge-core] _beeos/session/history sid=${o.sessionId} cursor=${i??"<head>"} limit=${n} turns=${l.length} updates=${d.updates.length} hasMore=${a.hasMore} nextCursor=${a.nextCursor??"<none>"}`),this.transport.sendResult(e.id,{updates:d.updates,...a.nextCursor?{nextCursor:a.nextCursor}:{},hasMore:a.hasMore})}emitCurrentStreamSnapshot(e,t){const s=e.pendingUserTurnUpdates;if(s&&s.length>0)for(const t of s)this.transport.sendSessionUpdate(e.sessionId,t);const i=e.assistantMq;if(i&&!i.closed){const s=i.peek(0);let n=0;if(t&&t.size>0)for(let e=0;e<s.length;e++){const i=s[e],o=i.sessionUpdate,r=i.toolCallId;if("tool_call"!==o&&"tool_call_update"!==o||!r||!t.has(r)){if("agent_message_chunk"===o){if(""===(i.content?.text??"").trim()&&e<s.length-1){const i=s[e+1];if(("tool_call"===i.sessionUpdate||"tool_call_update"===i.sessionUpdate)&&i.toolCallId&&t.has(i.toolCallId)){n=e+1;continue}}break}break}n=e+1}const o=resolveTurnId(e),r=o?{turnId:o}:void 0;for(let t=n;t<s.length;t++)this.transport.sendSessionUpdate(e.sessionId,s[t],r)}}async handleSessionList(e){let t;try{t=parseRuntimeSessionListPage(e.params)}catch(t){return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:t instanceof Error?t.message:"Invalid session/list params"})}const s=[];for(const[,e]of this.state.sessions)isBackgroundSessionKey(e.gatewaySessionKey)||this.cronIsolatedSessionKeys.has(e.gatewaySessionKey)||!t.includeDeleted&&this.state.deletedGatewaySessionKeys.has(e.gatewaySessionKey)||s.push({sessionId:e.sessionId,title:e.title||"New chat",cwd:e.cwd,updatedAt:e.lastStreamActivityTs??e.createdAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(e.gatewaySessionKey)});const i=this.getGateway();if(i?.isConnected)try{const e=await i.sessionsList();if(e&&"object"==typeof e&&Array.isArray(e.sessions)){const i=e.sessions;for(const e of i){const i=e,n=i.sessionKey??i.key??i.id??i.name;if(!n||"string"!=typeof n)continue;if(isBackgroundSessionKey(n))continue;if(this.cronIsolatedSessionKeys.has(n))continue;const o=deriveSessionId(n,this.instanceId),r=s.find(e=>e.sessionId===n||e.sessionId===o||this.state.sessions.get(e.sessionId)?.gatewaySessionKey===n);if(!0===i.deleted){if(this.state.deletedGatewaySessionKeys.add(n),!t.includeDeleted)continue}else this.state.deletedGatewaySessionKeys.delete(n);const a=i.label||cleanGatewayDerivedTitle(i.derivedTitle)||i.title||void 0,l=a&&!isPlaceholderTitle(a)?a:void 0,d=i.model,c=i.modelProvider;if(r){if(l&&isPlaceholderTitle(r.title)){r.title=l;const e=this.state.sessions.get(r.sessionId);e&&isPlaceholderTitle(e.title)&&(e.title=l)}const e=i.updatedAt;e&&(r.updatedAt=e),d&&(r.model=d),c&&(r.modelProvider=c)}else{const e=createSessionObject({gatewayKey:n,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,cwd:i.cwd,title:l});this.registerSession(e,{ensureVerbose:!1}),s.push({sessionId:e.sessionId,title:l||"",cwd:i.cwd,updatedAt:i.updatedAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(n),model:d,modelProvider:c})}}}}catch(e){this.logger.debug?.("[bridge-core] sessions.list enrichment failed:",e)}const n=t.includeDeleted?s:s.filter(e=>{const t=this.state.sessions.get(e.sessionId)?.gatewaySessionKey;return!t||!this.state.deletedGatewaySessionKeys.has(t)});n.sort((e,t)=>(t.updatedAt??0)-(e.updatedAt??0)||e.sessionId.localeCompare(t.sessionId)),this.transport.sendResult(e.id,{sessions:pageRuntimeSessions(n,t)})}async handleSessionRename(e){const t=e.params?.sessionId,s=e.params?.title;if(!t||!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, title"});const i=s.slice(0,64),n=this.state.sessions.get(t);if(n)try{const s=await this.dispatchLegacyConversationCapability("session.rename",n,{title:i});n.title=s.title,n.titleSource="manual",this.transport.sendResult(e.id,{sessionId:t,title:s.title})}catch(s){this.logger.warn?.(`[bridge-core] ACP rename projection deferred: ${s instanceof Error?s.message:String(s)}`),n.title=i,n.titleSource="manual",this.transport.sendResult(e.id,{sessionId:t,title:i})}else this.transport.sendAcpError(e.id,Errors.SessionNotFound)}static TITLE_TEMPLATES=[{match:["今日日报","日报"],zh:"今日工作日报",en:"Daily Work Log"},{match:["周报","周报总结","本周周报"],zh:"本周工作周报",en:"Weekly Work Report"},{match:["月报","月度汇报","月度总结"],zh:"本月工作月报",en:"Monthly Work Report"},{match:["工作日志","日志"],zh:"工作日志整理",en:"Work Log Review"},{match:["晨会","今日晨会"],zh:"今日晨会纪要",en:"Daily Standup Notes"},{match:["复盘","今日复盘"],zh:"今日工作复盘",en:"Daily Retrospective"}];static TITLE_SYSTEM_PROMPT='You are a conversation title generator. Your task is to generate a concise, human-readable title summarizing the topic of the user\'s question together with the assistant\'s answer.\n\n### Rules:\n- Length: 3-6 words in English, or 6-20 characters in Chinese/Japanese/Korean (up to 24 if needed to keep all key domain terms intact).\n- Language: Match the user\'s input language exactly. Mixed zh/en input → use the dominant language. Do NOT switch languages. (IM path injects Client UI locale via conversation-title.ts — keep ACP in sync with that module when changing examples.)\n- Extract the core topic (noun-centric) of the Q&A exchange, not the action verb.\n- Use the assistant\'s answer to disambiguate or enrich the topic when the question alone is vague (e.g. "帮我看下" + an answer about x402 → "x402 支付集成").\n- No punctuation, no quotation marks, no emojis, no markdown.\n- No prefix like "Title:" or "Here is". Output the title only.\n- Avoid generic words: "help", "question", "chat", "problem", "task".\n- Greetings and self-introductions ARE concrete topics (e.g. "自我介绍", "自己紹介", "Self Introduction") — never treat them as vague.\n- NEVER return placeholder / empty-chat titles such as "新会话", "新對話", "New chat", "New Conversation", "新しいチャット", "새 채팅", "แชตใหม่". Always invent a concrete noun-centric topic from whatever words are present.\n\n### Input Format:\nThe user message will contain both the question and the assistant\'s answer, delimited like this:\n[User Question]\n...question text...\n\n[Assistant Answer]\n...answer text (may be truncated with "..." in the middle)...\n\n### Output Format:\nRespond ONLY with valid JSON, no other text:\n{"title": "<your title here>"}\n\n### Examples:\nInput:\n[User Question]\n帮我写一个关于BeeOS设备接入的PRD\n\n[Assistant Answer]\n好的,这是 BeeOS 设备接入 PRD 的草稿:1. 目标 ...\nOutput: {"title": "BeeOS 设备接入 PRD"}\n\nInput:\n[User Question]\n帮我看下\n\n[Assistant Answer]\n你说的 x402 支付协议是一个基于 HTTP 402 的链上支付标准 ...\nOutput: {"title": "x402 支付协议解析"}\n\nInput:\n[User Question]\nWhat\'s the best way to integrate x402 payment protocol?\n\n[Assistant Answer]\nYou have three options: 1. Use the official SDK ...\nOutput: {"title": "x402 Payment Integration"}\n\nInput:\n[User Question]\n用一句话介绍一下你自己。\n\n[Assistant Answer]\n我是 BeeOS 智能助手,可以帮助你完成设备控制与任务协作。\nOutput: {"title": "自我介绍"}\n\nInput:\n[User Question]\nhi\n\n[Assistant Answer]\nHello! How can I help you today?\nOutput: {"title": "Greeting"}';matchTitleTemplate(e){const t=e.trim().toLowerCase();for(const s of AcpGatewayBridgeCore.TITLE_TEMPLATES)if(s.match.some(e=>e.toLowerCase()===t))return/[\u4e00-\u9fff]/.test(e)?s.zh:s.en}isGuardMessage(e){const t=e.trim();return[...t].length<3||[...t].every(e=>/[\p{P}\p{S}\s]/u.test(e))}truncateQuestionForTitle(e){return e.length>2e3?e.slice(0,1500):e}truncateAnswerForTitle(e){return e.length<=1e3?e:`${e.slice(0,500)}\n...\n${e.slice(-500)}`}generateTitleFromQA(e,t,s,i){if(this.isGuardMessage(s))return void this.logger.info?.(`[bridge-core] title-gen SKIP guard message sid=${t}`);const n=this.matchTitleTemplate(s);if(n){e.title=n,e.titleSource="template";const s=this.getGateway();return s?.isConnected&&s.sessionsPatch(e.gatewaySessionKey,{label:n}).catch(()=>{}),void this.transport.sendSessionUpdate(t,{sessionUpdate:"title_update",title:n})}const o=(process.env.AROUTER_BASE_URL||"").replace(/\/$/,""),r=process.env.AROUTER_API_KEY||"";if(!o||!r)return void this.logger.info?.(`[bridge-core] title-gen SKIP sid=${t}: no AROUTER env vars`);const a=`[User Question]\n${this.truncateQuestionForTitle(s)}\n\n[Assistant Answer]\n${this.truncateAnswerForTitle(i)}`;this.logger.info?.(`[bridge-core] title-gen CALLING LLM sid=${t} q="${s.slice(0,60)}" aLen=${i.length}`),fetch(`${o}/v1/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`},body:JSON.stringify({model:"openai/gpt-4o-mini",max_tokens:32,temperature:.3,messages:[{role:"system",content:AcpGatewayBridgeCore.TITLE_SYSTEM_PROMPT},{role:"user",content:a}]})}).then(async s=>{if(!s.ok){const e=await s.text().catch(()=>"");return void this.logger.info?.(`[bridge-core] title-gen FAILED sid=${t} status=${s.status} body=${e.slice(0,300)}`)}const i=await s.json(),n=i.choices?.[0]?.message?.content?.trim();if(!n)return;const o=n.replace(/^```json\s*/,"").replace(/```$/,"").trim();let r;try{r=JSON.parse(o).title?.trim()??""}catch{return void this.logger.debug?.(`[bridge-core] title-gen JSON parse failed: ${o}`)}if(!r)return;if(isPlaceholderTitle(r))return void this.logger.debug?.("[bridge-core] title-gen discarded: placeholder LLM title");if("manual"===e.titleSource)return void this.logger.debug?.("[bridge-core] title-gen discarded: user already renamed");this.logger.info?.(`[bridge-core] title-gen OK sid=${t} title="${r}"`),e.title=r,e.titleSource="auto";const a=this.getGateway();a?.isConnected&&a.sessionsPatch(e.gatewaySessionKey,{label:r}).catch(()=>{}),this.transport.sendSessionUpdate(t,{sessionUpdate:"title_update",title:r})}).catch(e=>{this.logger.info?.(`[bridge-core] title-gen ERROR sid=${t}: ${e instanceof Error?e.message:e}`)})}async tryGenerateTitleFromHistory(e,t,s){if("manual"===e.titleSource||"auto"===e.titleSource)return;const i=this.getGateway();if(!i?.isConnected)return;let n=s;if(!n)try{const t=await loadHistory(i,e.gatewaySessionKey);n=groupIntoTurns(t)}catch(e){return void this.logger.debug?.(`[bridge-core] title-gen loadHistory failed sid=${t}: ${e instanceof Error?e.message:e}`)}const o=n.find(e=>{if("user"!==e.role)return!1;if(!e.content?.trim())return!1;if("internal_system"===e.provenance?.kind)return!1;if(isCronPromptText(e.content))return!1;const t=cleanHistoryUserContent(e.content);return!!t&&!/^Sender\s*\(untrusted/i.test(t)});if(!o)return;const r=n.indexOf(o),a=n.slice(r+1).find(e=>"assistant"===e.role&&!!e.content?.trim());if(!a)return;const l=cleanHistoryUserContent(o.content),d=a.content.trim();l&&d&&this.generateTitleFromQA(e,t,l,d)}async handleSessionDelete(e){const t=e.params?.sessionId;if(!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const s=this.state.sessions.get(t);if(!s)return this.logger.info?.(`[bridge-core] session/delete sessionId=${t} NOT FOUND in local state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);const i=s.gatewaySessionKey,n=this.sessionMutationCoordinator.tryStartExclusive({sessionKey:i,label:"session_delete"},"acp");if(!n.ok)return void this.sendSessionBusy(s,e.id,"active_session_mutation",n.queueDepth);const o=n.token;try{if(this.hasActiveRun(s)){const t=this.sessionMutationCoordinator.gateSnapshot(i);return void this.sendSessionBusy(s,e.id,"active_session_mutation",t.queueDepth)}this.logger.info?.(`[bridge-core] session/delete sessionId=${t} gatewayKey=${i} gwConnected=${!!this.getGateway()?.isConnected}`);let n=!1;const o=this.getGateway();if(o?.isConnected&&i)try{await o.sessionsDelete(i),n=!0,this.logger.info?.(`[bridge-core] Gateway sessions.delete SUCCESS for key=${i}`)}catch(t){const s=t instanceof Error?t.message:String(t);if(this.logger.info?.(`[bridge-core] Gateway sessions.delete FAILED for key=${i}: ${s}`),!s.includes("Cannot delete the main session"))return void this.transport.sendResult(e.id,{deleted:!1});try{await o.sessionsReset(i),n=!0,this.logger.info?.(`[bridge-core] Gateway sessions.reset SUCCESS for key=${i}`)}catch(t){return this.logger.warn?.(`[bridge-core] Gateway sessions.reset also failed for key=${i}: ${t instanceof Error?t.message:t}`),void this.transport.sendResult(e.id,{deleted:!1})}}else i&&this.logger.warn?.(`[bridge-core] Gateway not connected, proceeding with local-only delete for key=${i}`);this.state.clearStreamIdleTimer(s),s.assistantMq?.close(),s.assistantMq=void 0,s.currentRunId&&this.state.promptsByRunId.delete(s.currentRunId);const r=new Set;for(const[e,s]of this.state.toolCallToSessionMap)s===t&&(this.state.toolCallToSessionMap.delete(e),r.add(e));for(const[e,t]of this.state.toolCallIdMap)r.has(t)&&this.state.toolCallIdMap.delete(e);this.state.sessions.delete(t),this.state.sessionsByGatewayKey.delete(i),this.state.deletedGatewaySessionKeys.add(i),this.transport.resetSessionUpdateCount(t),n&&this.onSessionDeleted?.(i),this.logger.info?.(`[bridge-core] session/delete DONE sessionId=${t} gatewayKey=${i} deleted=true`),this.transport.sendResult(e.id,{deleted:!0})}finally{this.sessionMutationCoordinator.releaseBeforeRuntime(o)}}async handleSessionClear(e){this.logger.info?.(`[bridge-core] handleSessionClear called, params=${JSON.stringify(e.params)}`);const t=e.params?.sessionId;if(!t)return this.logger.warn?.("[bridge-core] session/clear missing sessionId"),void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const s=this.state.sessions.get(t);if(!s)return this.logger.warn?.(`[bridge-core] session/clear sessionId=${t} not found in state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);this.logger.info?.(`[bridge-core] session/clear sessionId=${t} gatewayKey=${s.gatewaySessionKey}`);const i=this.getGateway(),n=i?.isConnected;if(!s.promptDone&&(void 0!==s.pendingPromptId||void 0!==s.currentRunId)){this.logger.info?.(`[bridge-core] session/clear: cancelling active run sid=${t} runId=${s.currentRunId}`);const o=s.writeGateToken;let r=!1;if(n&&o){const e=await this.sessionMutationCoordinator.requestCancel(o,i);r="recovery_required"===await this.handleCancellationResult(s,e)}else n&&await i.chatAbort(s.gatewaySessionKey,s.currentRunId);return void this.transport.sendError(e.id,Errors.AgentError.rpcCode,r?SESSION_RECOVERY_REQUIRED_MESSAGE:"Cancellation is still converging",r?{code:SESSION_RECOVERY_REQUIRED_CODE,stopReason:"interrupted"}:{code:"session_cancellation_pending",stopReason:"cancelled"})}try{await this.dispatchLegacyConversationCapability("session.clear",s,{}),this.transport.sendResult(e.id,{cleared:!0}),this.logger.info?.("[bridge-core] session/clear DONE, sent cleared=true")}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleSessionSetMode(e){const t=e.params?.sessionId,s=e.params?.mode,i=t?this.state.sessions.get(t):void 0;if(t&&s)if(i)try{const t=await this.dispatchLegacyConversationCapability("session.setMode",i,{mode:s});this.transport.sendResult(e.id,t)}catch(t){this.sendRuntimeCommandError(e.id,t)}else this.transport.sendAcpError(e.id,Errors.SessionNotFound);else this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, mode"})}async handleSessionSetModel(e){const t=e.params,s=null===t||"object"!=typeof t||Array.isArray(t)?void 0:t,i="string"==typeof s?.sessionId?s.sessionId:void 0;if(!i||!s||!Object.prototype.hasOwnProperty.call(s,"model"))return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, model"});const n=s.model;if(null!==n&&("string"!=typeof n||0===n.length||n.trim()!==n))return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"model must be a non-empty string or null"});const o=i?this.state.sessions.get(i):void 0;if(o)try{const t=await this.dispatchLegacyConversationCapability("session.setModel",o,{modelOverrideId:n});this.transport.sendResult(e.id,{model:t.effectiveModelId,modelProvider:t.modelProvider??null})}catch(t){this.sendRuntimeCommandError(e.id,t)}else this.transport.sendAcpError(e.id,Errors.SessionNotFound)}sendRuntimeCommandError(e,t){if(t instanceof RuntimeCommandError)switch(t.code){case"MISSING_PARAM":return void this.transport.sendAcpError(e,Errors.MissingParam,{...t.param?{param:t.param}:{}});case"INVALID_PARAMS":return void this.transport.sendAcpError(e,Errors.InvalidParams,{message:t.message});case"GATEWAY_NOT_CONNECTED":return void this.transport.sendAcpError(e,Errors.GatewayNotConnected);case"EXECUTION_FAILED":case"AGENT_DEFAULT_MODEL_NOT_CONFIGURED":case"OUTCOME_UNKNOWN":case"INSTANCE_EPOCH_MISMATCH":case"COMMAND_EXPIRED":case"IDEMPOTENCY_CONFLICT":case"UNAUTHORIZED_TARGET":return void this.transport.sendAcpError(e,Errors.InternalError,{message:t.message})}else this.transport.sendAcpError(e,Errors.InternalError,{message:t instanceof Error?t.message:"Internal error"})}createOperationId(){const e=randomUUID();return`${e.slice(0,14)}7${e.slice(15)}`}canonicalContextBase(e,t=this.createOperationId(),s="0"){const i=Date.now(),n=createHash("sha256").update(JSON.stringify(e)).digest("hex");return{operationId:t,runtimeInstanceUid:this.instanceId,dispatchEpoch:s,paramsHash:n,idempotencyKeyHash:createHash("sha256").update(t).digest("hex"),claimDeadline:new Date(i+3e4).toISOString(),executionDeadline:new Date(i+3e5).toISOString()}}acpExecutionAttempt(e){return createAcpCoreExecutionAttempt({operationId:e.context.operationId,runtimeEpoch:e.context.dispatchEpoch,resources:this.runtimeCommandResources,historyBoundary:this.localHistoryBoundary})}instanceCommand(e,t,s){return{name:e,params:t,context:{...this.canonicalContextBase(t,s.operationId,s.runtimeEpoch),scope:"instance"}}}async agentCommand(e,t,s,i){return{name:e,params:s,context:{...this.canonicalContextBase(s,i.operationId,i.runtimeEpoch),scope:"agent",agent:{runtimeLocalKey:t,runtimeAgentUid:await this.runtimeAgentIdentities.resolveOrCreate(t)}}}}async dispatchLegacyAgentCapability(e,t,s){const i=normalizeRuntimeAgentLocalKey(t),n=await this.runtimeLeases.claim(this.createOperationId());let o=!1;try{const t={name:e,params:s,context:{...this.canonicalContextBase(s,n.operationId,n.runtimeEpoch),scope:"agent",agent:{runtimeLocalKey:i,runtimeAgentUid:await this.runtimeAgentIdentities.resolveOrCreate(i)}}},r=this.runtimeCommands.dispatch(t,this.acpExecutionAttempt(t));return o=!0,await r.completion}finally{o||await this.runtimeLeases.release(n)}}async dispatchLegacyInstanceCapability(e,t){const s=await this.runtimeLeases.claim(this.createOperationId());let i=!1;try{const n={name:e,params:t,context:{...this.canonicalContextBase(t,s.operationId,s.runtimeEpoch),scope:"instance"}},o=this.runtimeCommands.dispatch(n,this.acpExecutionAttempt(n));return i=!0,await o.completion}finally{i||await this.runtimeLeases.release(s)}}async handleMcpList(e){const t=await this.dispatchLegacyInstanceCapability("mcp.list",{});this.transport.sendResult(e.id,{servers:Object.fromEntries(t.servers.map(e=>[e.name,{transport:e.transport,...e.url?{url:e.url}:{},...e.command?{command:e.command}:{},...e.args?{args:[...e.args]}:{},...e.cwd?{cwd:e.cwd}:{},...e.connectionTimeoutMs?{connectionTimeoutMs:e.connectionTimeoutMs}:{}}]))})}async handleMcpSet(e){const t=e.params&&"object"==typeof e.params?e.params:{},s="string"==typeof t.name?t.name:"";let i;try{if(requireSecretFreeMcpServerName(s),!t.server||"object"!=typeof t.server||Array.isArray(t.server))throw new Error("mcp/set requires name and server");const e=t.server,n=extractMcpCredentialMaterial(e),o={...e};delete o.headers,delete o.env;const r=e.transport??("string"==typeof e.command?"stdio":"string"==typeof e.url?"streamable-http":void 0),a=normalizeMcpPublicServerConfig({...o,transport:r}),l=createHash("sha256").update(canonicalizeJcs(a)).digest("hex");i={definition:{definitionRef:`acp-mcp:sha256:${l}`,version:"local-v1",sha256:l,...n?{credentialRef:await requireCredentialReferencePort(this.mcpCredentialReferences).put({serverName:s,material:n})}:{}},config:structuredClone(a)}}catch(t){return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:t instanceof Error?t.message:"Invalid MCP server config"})}this.runtimeCommandResources.registerMcpDefinition?.(i.definition,i.config);try{await this.dispatchLegacyInstanceCapability("mcp.set",{name:s,definition:i.definition})}catch(e){if(i.definition.credentialRef){!await this.mcp.removeCredentialIfUnreferenced(i.definition.credentialRef)&&this.mcpCredentialReferences?.owns(i.definition.credentialRef)&&await this.mcpCredentialReferences.remove(i.definition.credentialRef)}throw e}this.transport.sendResult(e.id,{ok:!0,restarting:!1})}async handleMcpUnset(e){const t=e.params&&"object"==typeof e.params?e.params:{};let s;try{s=requireSecretFreeMcpServerName(t.name)}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"mcp/unset requires name"})}await this.dispatchLegacyInstanceCapability("mcp.unset",{name:s}),this.transport.sendResult(e.id,{ok:!0,restarting:!1})}async dispatchLegacyConversationCapability(e,t,s){const i=await this.runtimeLeases.claim(this.createOperationId());let n=!1;try{const o={name:e,params:s,context:{...this.canonicalContextBase(s,i.operationId,i.runtimeEpoch),scope:"conversation",conversation:{runtimeConversationUid:t.sessionId,runtimeSessionKey:t.gatewaySessionKey}}},r=this.runtimeCommands.dispatch(o,this.acpExecutionAttempt(o));return n=!0,await r.completion}finally{n||await this.runtimeLeases.release(i)}}async dispatchLegacyTerminalCapability(e,t,s,i){const n=await this.runtimeLeases.claim(this.createOperationId());let o=!1;try{const r={name:e,params:i,context:{...this.canonicalContextBase(i,n.operationId,n.runtimeEpoch),scope:"terminal",terminal:{terminalId:t,clientId:s}}},a=this.runtimeCommands.dispatch(r,this.acpExecutionAttempt(r));return o=!0,await a.completion}finally{o||await this.runtimeLeases.release(n)}}async dispatchLegacyCanvasNotification(e,t){const s=t&&"object"==typeof t?t:{},i=e.replace("/","."),n="canvas.toggle"===i?{visible:!0===s.visible||!0===s.enabled}:"canvas.reference"===i?{surfaceId:"string"==typeof s.surfaceId?s.surfaceId:""}:"canvas.dimensions"===i?{width:Number(s.width),height:Number(s.height),..."number"==typeof s.devicePixelRatio?{devicePixelRatio:s.devicePixelRatio}:{}}:{},o="string"==typeof s.sessionId?s.sessionId:"acp",r="string"==typeof s.canvasId?s.canvasId:o,a=await this.runtimeLeases.claim(this.createOperationId());let l=!1;try{const e={name:i,params:n,context:{...this.canonicalContextBase(n,a.operationId,a.runtimeEpoch),scope:"canvas",canvas:{canvasId:r,sessionId:o}}},t=this.runtimeCommands.dispatch(e,this.acpExecutionAttempt(e));l=!0,await t.completion}finally{l||await this.runtimeLeases.release(a)}}async applyCanonicalCanvasControl(e,t){if("canvas/reference"===e){const e=this.state.resolveSessionFromParams(t);if(!e)throw new RuntimeCommandError("INVALID_PARAMS","canvas session not found");const s="string"==typeof t.surfaceId?t.surfaceId:void 0;return void(e.referencedSurfaceId=s||void 0)}if(!this.handleCanvasNotification)throw new RuntimeCommandError("INVALID_PARAMS","canvas control delegate is not configured");await this.handleCanvasNotification(e,t)}async dispatchLegacyCronCapability(e,t){const s=normalizeRuntimeAgentLocalKey(this.config.gateway.agentId),i=await this.runtimeLeases.claim(this.createOperationId());let n=!1;try{const o="cron.add"===e?{...t,runtimeCronJobUid:`runtime-cron:${i.operationId}`}:"cron.run"===e?{...t,runToken:`runtime-run:${i.operationId}`}:t,r={name:e,params:o,context:{...this.canonicalContextBase(o,i.operationId,i.runtimeEpoch),scope:"agent",agent:{runtimeLocalKey:s,runtimeAgentUid:await this.runtimeAgentIdentities.resolveOrCreate(s)}}},a=this.runtimeCommands.dispatch(r,this.acpExecutionAttempt(r));return n=!0,await a.completion}finally{n||await this.runtimeLeases.release(i)}}validateConnectedGateway(){if(!this.getGateway()?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected")}async finishClaimedLegacyAgentMutation(e,t){let s=!1;try{const e=await t(),i=this.runtimeCommands.dispatch(e,this.acpExecutionAttempt(e));s=!0;const n=await i.completion;return queueMicrotask(()=>{this.observeAgentProjection()}),n}finally{s||await this.runtimeLeases.release(e)}}async observeAgentProjection(){await(async(e,t)=>{if(!t)return;let s;try{await Promise.race([Promise.resolve().then(t),new Promise((t,i)=>{s=setTimeout(()=>i(new Error(`${e} timed out`)),this.projectionObservationTimeoutMs)})])}catch(t){this.logger.warn?.(`[acp] ${e} failed: ${t instanceof Error?t.message:String(t)}`)}finally{s&&clearTimeout(s)}})("agent projection sync",this.triggerAgentSync)}async handleAgentCreate(e){try{const t=e.params?.name?.trim()??"";if(!t)throw new RuntimeCommandError("INVALID_PARAMS","Missing agent name");this.validateConnectedGateway();const s=this.createOperationId(),i=await this.runtimeLeases.claim(s),n=await this.finishClaimedLegacyAgentMutation(i,async()=>{const s=await this.runtimeAgentIdentities.allocateCreate(i,"agent.create"),n=Object.entries(e.params?.files??{}).filter(e=>"string"==typeof e[1]).map(([e,t])=>({name:e,contentRef:this.runtimeCommandResources.registerContent(t)}));return this.instanceCommand("agent.create",{runtimeAgentUid:s.runtimeAgentUid,name:t,...e.params?.identity?{identity:e.params.identity}:{},...e.params?.model?{defaultModelId:e.params.model}:{},...n.length?{bootstrapFiles:n}:{}},i)});this.transport.sendResult(e.id,{agentId:n.runtimeLocalKey,platformAgentId:this.resolvePlatformAgentId(n.runtimeLocalKey)??"",workspace:n.workspacePath})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleApplyTemplate(e){try{const t=e.params?.name?.trim()??"";if(!t)throw new RuntimeCommandError("INVALID_PARAMS","Missing agent name");const s=e.params?.packageUrl?.trim()??"";if(!s)throw new RuntimeCommandError("MISSING_PARAM","Missing packageUrl","packageUrl");this.validateConnectedGateway();const i=await this.runtimeCommandResources.registerArtifact(s,e.params?.sha256),n=this.createOperationId(),o=await this.runtimeLeases.claim(n),r=await this.finishClaimedLegacyAgentMutation(o,async()=>{const s=await this.runtimeAgentIdentities.allocateCreate(o,"agent.applyTemplate");return this.instanceCommand("agent.applyTemplate",{runtimeAgentUid:s.runtimeAgentUid,name:t,artifact:i,...e.params?.identity?{identity:e.params.identity}:{},...e.params?.model?{defaultModelId:e.params.model}:{}},o)});this.transport.sendResult(e.id,{agentId:r.runtimeLocalKey,platformAgentId:this.resolvePlatformAgentId(r.runtimeLocalKey)??"",workspace:r.workspacePath})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleAgentUpdate(e){try{const t=e.params?.agentId?.trim()??"";if(!t)throw new RuntimeCommandError("MISSING_PARAM","Missing agentId","agentId");const s={...void 0!==e.params?.name?{name:e.params.name}:{},...void 0!==e.params?.model?{defaultModelId:e.params.model}:{},...void 0!==e.params?.identity?{identity:e.params.identity}:{}};if(0===Object.keys(s).length)throw new RuntimeCommandError("INVALID_PARAMS","No updatable fields provided (name/model/identity)");this.validateConnectedGateway();const i=await this.runtimeLeases.claim(this.createOperationId()),n=await this.finishClaimedLegacyAgentMutation(i,()=>this.agentCommand("agent.update",t,s,i));this.transport.sendResult(e.id,{agentId:n.runtimeLocalKey})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleAgentDelete(e){try{const t=e.params?.agentId?.trim()??"";if(!t)throw new RuntimeCommandError("MISSING_PARAM","Missing agentId","agentId");if("main"===normalizeRuntimeAgentLocalKey(t))throw new RuntimeCommandError("INVALID_PARAMS",'"main" cannot be deleted; delete the instance instead');this.validateConnectedGateway();const s=await this.runtimeLeases.claim(this.createOperationId()),i=await this.finishClaimedLegacyAgentMutation(s,()=>this.agentCommand("agent.delete",t,void 0===e.params?.deleteFiles?{}:{deleteFiles:e.params.deleteFiles},s));this.transport.sendResult(e.id,{agentId:i.runtimeLocalKey,...void 0===i.removedBindings?{}:{removedBindings:i.removedBindings}})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleAgentList(e){try{const t=await this.dispatchLegacyInstanceCapability("agent.listProjection",{}),s=t.agents.map(e=>({agentId:e.runtimeLocalKey,name:e.name,...e.emoji?{emoji:e.emoji}:{},...e.avatarUrl?{avatarUrl:e.avatarUrl}:{}})),i={...t.defaultRuntimeLocalKey?{defaultId:t.defaultRuntimeLocalKey}:{},agents:s};this.transport.sendResult(e.id,i)}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleModelsList(e){try{this.transport.sendResult(e.id,await this.dispatchLegacyInstanceCapability("models.list",{}))}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleSkillsList(e){try{const t=await this.dispatchLegacyAgentCapability("skills.list",e.params?.agentId,{});this.transport.sendResult(e.id,{skills:t.skills.map(e=>({name:e.name,description:e.description??"",...e.emoji?{emoji:e.emoji}:{},...e.always?{always:!0}:{},...e.bundled?{bundled:!0}:{},...e.enabled?{}:{disabled:!0},skillKey:e.skillKey,...e.iconUrl?{iconUrl:e.iconUrl}:{}}))})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async fetchSimplifiedSkills(e){const t=this.getGateway();if(!t?.isConnected)return[];try{const s=await t.skillsStatus(e);if(!s?.skills)return[];const i=s.skills.filter(e=>!1!==e.eligible||!0===e.disabled).sort((e,t)=>e.always&&!t.always?-1:!e.always&&t.always?1:e.name.localeCompare(t.name)).map(e=>({name:e.name,description:e.description,emoji:e.emoji,always:e.always||void 0,bundled:e.bundled||void 0,disabled:e.disabled||void 0,skillKey:e.skillKey||void 0,iconUrl:void 0})),n=await this.getSkillsDir(e);for(const e of i){const t=e.skillKey??e.name;try{const s=await readFile(join(n,t,".beeos-meta.json"),"utf8"),i=JSON.parse(s);i?.iconUrl&&(e.iconUrl=i.iconUrl)}catch{}}return i}catch(e){return this.logger.warn?.(`[bridge-core] Failed to fetch skills: ${e instanceof Error?e.message:e}`),[]}}async pushSkillsList(e){const t=await this.fetchSimplifiedSkills(e);this.transport.sendNotification("session/update",{sessionId:"_system",update:{sessionUpdate:"available_commands_update",commands:t}}),this.writeObs({component:"bridge-core",domain:"lifecycle",name:"skills.pushed",severity:"debug",payload:{count:t.length}})}getOpenclawHome(){return process.env.OPENCLAW_HOME||join(homedir(),".openclaw")}async getSkillsDir(e){const t=this.getOpenclawHome();if(!e||"main"===e)return join(t,"skills");const s=await this.resolveAgentWorkspaceDir(e);return join(s??join(t,`workspace-${e}`),"skills")}async resolveAgentWorkspaceDir(e){const t=this.getGateway();if(!t?.isConnected)return null;try{const s=await t.request("agents.list",{}),i=normalizeAgentId(e),n=s?.agents?.find(e=>normalizeAgentId(e.id)===i);return n?.workspace??null}catch(t){return this.logger.warn?.(`[bridge-core] resolveAgentWorkspaceDir("${e}") failed: ${t instanceof Error?t.message:t}`),null}}async handleSkillsInstall(e){const t=e.params,s=t?.zipUrl,i=t?.files,n=t?.agentId;let o;try{o=validateSkillSlug(t?.slug||t?.name||"")}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Invalid or missing skill slug"})}if(i?.length||s)try{let r;if(s){r=parseSkillZip(await fetchWithRetry(s,{maxRetries:4,logger:this.logger}))}else{if(!i?.length)throw new RuntimeCommandError("INVALID_PARAMS","Missing skill files or zipUrl");r=new Map;for(const e of i){const t=normalizeSkillArtifactPath(e.path),s=await fetchWithRetry(e.downloadUrl,{maxRetries:4,logger:this.logger});r.set(t,new Uint8Array(s))}}if(t?.iconUrl&&r.set(".beeos-meta.json",(new TextEncoder).encode(JSON.stringify({iconUrl:t.iconUrl}))),!this.runtimeCommandResources.registerParsedArtifact)throw new RuntimeCommandError("EXECUTION_FAILED","ACP skill artifact ingress is not configured");const a=this.runtimeCommandResources.registerParsedArtifact({manifest:{slug:o,version:"0.0.0"},workspaceFiles:r});await this.dispatchLegacyAgentCapability("skills.install",n,{artifact:a}),this.transport.sendResult(e.id,{success:!0}),this.onSkillInstalled?.(o,resolveSkillInstallDir(await this.getSkillsDir(n),o)),await this.pushSkillsList(n)}catch(t){this.logger.error?.(`[bridge-core] Failed to install skill "${o}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Install failed: ${t instanceof Error?t.message:String(t)}`})}else this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skill files or zipUrl"})}async handleSkillsUninstall(e){const t=e.params,s=t?.agentId;let i;try{i=validateSkillSlug(t?.slug||t?.name||"")}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Invalid or missing skill name"})}try{await this.dispatchLegacyAgentCapability("skills.uninstall",s,{skillKey:i}),this.transport.sendResult(e.id,{success:!0}),await this.pushSkillsList(s)}catch(t){this.logger.error?.(`[bridge-core] Failed to uninstall skill "${i}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Uninstall failed: ${t instanceof Error?t.message:String(t)}`})}}async handleSkillsUpdate(e){const t=e.params,s=t?.skillKey,i=t?.enabled,n=t?.agentId;if(s&&"boolean"==typeof i)try{await this.dispatchLegacyAgentCapability("skills.setEnabled",n,{skillKey:s,enabled:i}),this.logger.info?.(`[bridge-core] Updated skill "${s}" enabled=${i}${n?` for agent "${n}"`:""}`),this.transport.sendResult(e.id,{ok:!0,skillKey:s,enabled:i}),await this.pushSkillsList(n)}catch(t){this.logger.error?.(`[bridge-core] Failed to update skill "${s}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Update failed: ${t instanceof Error?t.message:String(t)}`})}else this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skillKey or enabled"})}async handleSessionPrompt(e){const t=e.params?.sessionId,s=e.params?.prompt;if(!t||!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, prompt"});const{session:i,created:n}=await this.resolveOrCreateSession(t);n&&this.onSessionLoaded?.(i.sessionId,i.gatewaySessionKey,i.platformAgentId);const o=i.sessionId;this.state.lastActiveSessionId=o,this.onPromptStart?.();const r=this.getGateway();if(!r?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);if(this.hasActiveRun(i)){const t=this.sessionMutationCoordinator.gateSnapshot(i.gatewaySessionKey);return void this.sendSessionBusy(i,e.id,"active_run",t.queueDepth)}try{await this.ensureVerboseFull(i)}catch(t){return this.releaseSessionWriteGate(i),void this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Failed to prepare agent context: ${t instanceof Error?t.message:String(t)}`})}let a=extractPromptBlocks(sanitizePromptPayload(s));const l=a.filter(e=>"text"===e.type&&"text"in e&&"string"==typeof e.text).map(e=>e.text).join("\n");if(i.fileArtifactDisplayMode=prefersChatFileArtifacts(l)?"chat":"auto",!i.title||isPlaceholderTitle(i.title)){const e=a.find(e=>"text"===e.type&&e.text);if(e&&"text"in e&&e.text){const t=stripTransportMetadata(e.text).trim();if(this.logger.info?.(`[bridge-core] title-gen sid=${o} existing="${i.title}" cleanText="${t.slice(0,80)}" isPlaceholder=${isPlaceholderTitle(t)}`),t&&!isPlaceholderTitle(t)){const e=t.slice(0,64).replace(/\n/g," ").trim();i.title=e,i.titleSource="truncated",r?.isConnected&&e&&r.sessionsPatch(i.gatewaySessionKey,{label:e}).catch(e=>{this.logger.debug?.(`[bridge-core] sessionsPatch (title) failed: ${e instanceof Error?e.message:e}`)}),this.transport.sendSessionUpdate(o,{sessionUpdate:"title_update",title:e})}}}const d=buildResolutionPlan(a);if(d.refs.length>0)try{const e=await resolveResources(d,this.writeObs);a=applyResolutions(a,e)}catch(e){this.logger.warn?.(`[bridge-core] Resource pre-resolution failed: ${e instanceof Error?e.message:e}`)}let c;if(this.beeosFileResolver){const e=this.beeosFileResolver.buildResolutionPlan(a);if(e.refs.length>0)try{c=await this.beeosFileResolver.resolveMetadataForPrompt(e)}catch(e){this.logger.warn?.(`[bridge-core] BeeOS file resolution failed: ${e instanceof Error?e.message:e}`)}}const m=toGatewayPrompt(a,{beeosFileResolutionsByUri:c});if("error"in m)return this.releaseSessionWriteGate(i),void this.transport.sendAcpError(e.id,Errors.PromptConvertFailed,{message:m.error});const{message:u,attachments:p}=m,h=resolveStandaloneSlashCommandName(a,u)??resolveSlashCmd(a,u),g="string"==typeof h;if(h&&this.handleSlashCommand){const t=this;if(await(async()=>{try{return await t.handleSlashCommand(h)}catch(e){return t.logger.warn?.(`[bridge-core] /${h} handler failed: ${e instanceof Error?e.message:e}`),!1}})())return this.emitPromptAsUserSessionUpdates(i,a,resolveTurnId(i)),this.transport.sendSessionUpdate(o,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:`Loading ${h}...`},timestamp:(new Date).toISOString()},{turnId:resolveTurnId(i)}),this.transport.sendResult(e.id,{stopReason:"end_turn"}),void this.releaseSessionWriteGate(i)}const y="chat.send";let f=g?u:withUserMessagePrefix(u);if(this.config.canvasEnabled&&!g&&!0===i.canvasActive&&!i.channelType){const e=this.getCanvasState?.();if(e){const t=e.resolveCanvasForSession(i.gatewaySessionKey);f=`${buildActiveSurfacesContext(e,t)??"[Canvas] Canvas panel is available. Use render_ui for structured/visual content."}\n\n${f}`}const t=i.referencedSurfaceId;t&&(f=`${CANVAS_UPDATE_HINT}\n[IMPORTANT: The user selected canvas surface "${t}" for editing. You MUST call render_ui with id="${t}" to UPDATE this existing surface. Do NOT create a new surface with a different id.]\n${f}`,i.referencedSurfaceId=void 0)}if(!g){const t=parseMentionsParam(e.params?.mentions);if(t.length>0){i.activeMentions=t;f=`${buildMentionInstruction(t)}\n${f}`,this.logger.debug?.(`[bridge-core] delegation mention instruction injected for ${t.length} agent(s)`)}else i.activeMentions=void 0}if(g&&this.logger.info?.(`[bridge-core] slash command passthrough sessionId=${o} command=/${h}`),this.hasActiveRun(i)){const t=this.sessionMutationCoordinator.gateSnapshot(i.gatewaySessionKey);return void this.sendSessionBusy(i,e.id,"active_run_after_prepare",t.queueDepth)}const w=this.sessionMutationCoordinator.tryStartInteractive({sessionKey:i.gatewaySessionKey,label:"session/prompt"},"acp");if(!w.ok)return void this.sendSessionBusy(i,e.id,w.reason,w.queueDepth);i.writeGateToken=w.token,this.state.resetForNewRun(i,o,this.transport),i.pendingPromptId=e.id,i.gatewayMethod=y;const I=resolveTurnId(i);this.emitPromptAsUserSessionUpdates(i,a,I),this.transport.sendSessionUpdate(o,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:""},timestamp:(new Date).toISOString()},I?{turnId:I}:void 0);const v=extractCronJobId(u);v&&(i.cronJobId=v);const S=p.length>0?p.map(e=>({type:e.type,content:e.content,...e.mimeType?{mimeType:e.mimeType}:{},...e.fileName?{fileName:e.fileName}:{}})):void 0,C=`req_${randomUUID().replace(/-/g,"")}`,R={sessionKey:i.gatewaySessionKey,message:f,deliver:!1,idempotencyKey:randomUUID()};S?.length&&(R.attachments=S);if(!r.sendFrame({type:"req",id:C,method:y,params:R}))return i.pendingPromptId=void 0,i.assistantMq?.close(),i.assistantMq=void 0,i.pendingUserTurnUpdates=void 0,this.releaseSessionWriteGate(i),void this.transport.sendAcpError(e.id,Errors.SendFailed,{message:"failed to send prompt to gateway"});i.gatewayRequestId=C,this.state.promptsByGatewayRequestId.set(C,i)}replayUserTurnAsChunks(e,t,s,i,n,o,r){const a=splitUserReplayBlocks(s),l=o||void 0!==r?{...o?{turnId:o}:{},...void 0!==r?{seq:r}:{}}:void 0;for(const s of a){let o;if("text"===s.type){if(!s.text)continue;o={type:"text",text:s.text}}else o="resource_link"===s.type?{type:"resource_link",uri:s.uri??"",...s.fileName?{name:s.fileName}:{},...s.mimeType?{mimeType:s.mimeType}:{}}:{type:s.type,...s.mimeType?{mimeType:s.mimeType}:{},...s.fileName?{fileName:s.fileName}:{}};e.sendSessionUpdate(t,{sessionUpdate:"user_message_chunk",content:o,...i?{metadata:i}:{},...n?{timestamp:n}:{}},l)}}async replayAssistantTurnAsStream(e,t,s,i){const n=e=>{if(i||void 0!==e)return{...i?{turnId:i}:{},...void 0!==e?{seq:e}:{}}},o=new Set,r=(s,i,r)=>{if(!s?.length)return;const a=n(r);for(const n of s)n.uri&&!o.has(n.uri)&&(o.add(n.uri),e.sendSessionUpdate(t,{sessionUpdate:"agent_message_chunk",content:{type:"resource_link",uri:n.uri,name:n.name??"file",...n.mimeType?{mimeType:n.mimeType}:{}},...i?{timestamp:i}:{}},a))},a=n(s._seq);if(!s.sourceMessages?.length){const i={sessionUpdate:"history_turn",role:s.role,content:s.content,reasoning:s.reasoning,toolCalls:s.toolCalls,...s.timestamp?{timestamp:s.timestamp}:{}};return s.metadata&&(i.metadata=s.metadata),e.sendSessionUpdate(t,i,a),void r(s.files,s.timestamp,s._seq)}for(const i of s.sourceMessages){const o=i.updateType??"agent_message_chunk",a=n(i._seq);switch(o){case"agent_message_chunk":e.sendSessionUpdate(t,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:i.content},...i.timestamp?{timestamp:i.timestamp}:{},...i.metadata?{metadata:i.metadata}:{}},a);break;case"agent_thought_chunk":e.sendSessionUpdate(t,{sessionUpdate:"agent_thought_chunk",content:{type:"text",text:i.content},...i.timestamp?{timestamp:i.timestamp}:{}},a);break;case"tool_call":{const n=i.toolCallId??"",o=s.toolCalls?.find(e=>e.id===n),r=i.toolName??"tool",l=i.content??"";let d={};if(l)try{d=JSON.parse(l)}catch{}e.sendSessionUpdate(t,buildToolCallPayload({toolCallId:n,toolName:r,args:d,kind:o?.kind,status:o?.status??"in_progress",timestamp:i.timestamp}),a);break}case"tool_call_update":{const n=i.toolCallId??"",o=s.toolCalls?.find(e=>e.id===n),l=i.toolName??o?.name??"tool",d=i.content??o?.output??"",c=await this.resolveReplayResourceLinks(n,l,i.files);e.sendSessionUpdate(t,buildToolCallUpdatePayload({toolCallId:n,toolName:l,outputText:d,kind:o?.kind,status:o?.status??"completed",resourceLinks:c,timestamp:i.timestamp}),a),r(c,i.timestamp,i._seq);break}}}r(s.files,s.timestamp,s._seq)}async resolveReplayResourceLinks(e,t,s){let i=s?.map(e=>({uri:e.uri,name:e.name??"file",...e.mimeType?{mimeType:e.mimeType}:{}}));if(i?.length)return i;if(i=await readEnrichmentCache(e).catch(()=>{}),i?.length)return i;if("beeos_upload_file"!==t)return;const n=await readUploadToolResult(e).catch(()=>{});if(!n)return;const o=buildToolResultSessionContentBlocks(t,n).filter(e=>"resource_link"===e.content?.type&&e.content.uri).map(e=>({uri:e.content.uri,name:e.content.name??"file",...e.content.mimeType?{mimeType:e.content.mimeType}:{}}));return o.length>0?o:void 0}emitPromptAsUserSessionUpdates(e,t,s){const i=(new Date).toISOString(),n=s?{turnId:s}:void 0;for(const o of t){const t=this.toUserSessionUpdateContent(o);if(!t)continue;const r={sessionUpdate:"user_message_chunk",content:t,timestamp:i};s&&void 0===r._turnId&&(r._turnId=s),this.transport.sendSessionUpdate(e.sessionId,r,n),e.pendingUserTurnUpdates?.push(r)}}toUserSessionUpdateContent(e){if("text"===e.type){if(!e.text)return;return{type:"text",text:e.text}}if("image"===e.type){const t=e.mimeType??e.mime_type??"application/octet-stream",s=e.fileName??e.file_name??e.name;if(!e.data&&!e.uri)return;return{type:"image",...e.data?{data:e.data}:{},...e.uri?{uri:e.uri}:{},...t?{mimeType:t}:{},...s?{fileName:s}:{}}}if("resource_link"===e.type){if(!e.uri)return;return{type:"resource_link",uri:e.uri,...e.title?{title:e.title}:{},...e.name?{name:e.name}:{},...e.mimeType??e.mime_type?{mimeType:e.mimeType??e.mime_type}:{}}}if("file"===e.type){const t=e.mimeType??e.mime_type??"application/octet-stream",s=e.fileName??e.file_name??e.filename??e.name;if(!e.data&&!e.text&&!e.uri)return;return{type:"file",...e.data?{data:e.data}:{},...e.text?{text:e.text}:{},...e.uri?{uri:e.uri}:{},...t?{mimeType:t}:{},...s?{fileName:s}:{}}}if("resource"===e.type){const t=e.resource??{},s=t.uri??e.uri,i=t.mimeType??t.mime_type??e.mimeType??e.mime_type,n=t.text??e.text,o=t.data??e.data,r=t.fileName??t.file_name??t.filename??t.name??e.fileName??e.file_name??e.filename??e.name;if(!s&&!n&&!o)return;return{type:"resource",resource:{...s?{uri:s}:{},...i?{mimeType:i}:{},...n?{text:n}:{},...o?{data:o}:{},...r?{fileName:r}:{}}}}}async handleSessionCancel(e,t){const s=t&&"object"==typeof t&&"sessionId"in t?t.sessionId:void 0;if(!s)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.MissingParam,{param:"sessionId"}));const i=this.state.sessions.get(s);if(!i)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.SessionNotFound));const n=i.activeRunBinding??this.observeActiveRun(i);if(!n){const t=this.sessionMutationCoordinator.snapshot(i.gatewaySessionKey);if("exclusive"===t?.attachmentPolicy&&void 0!==e){const t=this.sessionMutationCoordinator.gateSnapshot(i.gatewaySessionKey);return void this.sendSessionBusy(i,e,"exclusive_session_mutation",t.queueDepth)}return void(void 0!==e&&this.transport.sendResult(e,{}))}const o=await this.runtimeLeases.claim(this.createOperationId());let r=!1;try{const s=t&&"object"==typeof t&&"reason"in t&&"string"==typeof t.reason?t.reason:void 0,i={targetMessageId:n.targetMessageId,...s?{reason:s}:{}},a={name:"session.cancel",params:i,context:{...this.canonicalContextBase(i,o.operationId,o.runtimeEpoch),scope:"conversation",historyGeneration:n.historyGeneration,conversation:{runtimeConversationUid:n.runtimeConversationUid,runtimeSessionKey:n.runtimeSessionKey}}},l=this.runtimeCommands.dispatch(a,this.acpExecutionAttempt(a));r=!0,await l.completion,void 0!==e&&this.transport.sendResult(e,{})}catch(t){void 0!==e&&this.sendRuntimeCommandError(e,t)}finally{r||await this.runtimeLeases.release(o)}}hasActiveRun(e){return!e.promptDone&&(void 0!==e.pendingPromptId||void 0!==e.currentRunId||void 0!==e.gatewayRequestId)}observeLifecycle(e,t,s){observeLifecyclePromise(e,this.logger,{component:"bridge-core",action:t,...s?{sessionId:s.sessionId,gatewaySessionKey:s.gatewaySessionKey}:{}})}async handleCancellationResult(e,t){return t.ok?(this.observeLifecycle(t.terminalObservation.then(t=>{if(!t.ok)return this.publishCancellationRecovery(e)}),"cancel_terminal_observation",e),"accepted"):"recovery"in t?(await this.publishCancellationRecovery(e),"recovery_required"):"terminal_already_observed"}async publishCancellationRecovery(e){await this.terminateRunOutput(e,{kind:"failed",mode:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE})}releaseSessionWriteGate(e){if(!e.writeGateToken)return;const t=e.writeGateToken;e.writeGateToken=void 0,this.sessionMutationCoordinator.releaseBeforeRuntime(t)}logRuntimeFailure(e,t){this.logger.warn?.(`[bridge-core] runtime_failure_classified session_id=${e.sessionId} gateway_session_key=${e.gatewaySessionKey} run_id=${e.currentRunId??""} gateway_request_id=${e.gatewayRequestId??""} error_code=${t.errorCode} stop_reason=${t.stopReason} source=${t.source} runtime=${t.runtimeKind}`)}async commitRunOutputTerminal(e,t){const s=this.state.sessions.get(e.sessionId);if(!s||s.gatewaySessionKey!==e.gatewaySessionKey)throw new Error(`run output lifecycle session unavailable: ${e.sessionId}`);await this.commitRunOutputTerminalForSession(s,t)}async commitRunOutputTerminalForSession(e,t){if("complete"!==t.kind&&"cancelled"!==t.kind)return"interrupted"===t.mode?(t.classification&&this.logRuntimeFailure(e,t.classification),void await this.endRun(e,{kind:"interrupted",stopReason:t.classification?.stopReason??"interrupted",message:t.message,errorCode:t.errorCode})):void await this.endRun(e,{kind:"error",code:t.code,message:t.message});await this.endRun(e,{kind:"complete",stopReason:t.stopReason})}async terminateRunOutput(e,t){const s=resolveTurnId(e);s&&this.runOutputRouter.hasRoute(s)?await this.runOutputRouter.terminal(s,t):await this.commitRunOutputTerminalForSession(e,t)}sendSessionBusy(e,t,s,i){this.logger.warn?.(`[bridge-core] session_write_gate_busy session_id=${e.sessionId} gateway_session_key=${e.gatewaySessionKey} reason=${s} queue_depth=${i}`),this.transport.sendError(t,Errors.AgentError.rpcCode,"Agent is busy",{code:"session_busy",reason:s,queueDepth:i}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:"interrupted",error:INTERRUPTED_PUBLIC_MESSAGE,errorCode:"session_busy"},{turnId:`req:${String(t)}`})}async endRun(e,t){if(e.promptDone||e.mutationTerminalCommitting)return this.logger.debug?.(`[bridge-core] endRun skipped (promptDone=true) sessionId=${e.sessionId} outcome=${t.kind}`),!1;if(void 0===e.pendingPromptId&&void 0===e.currentRunId&&void 0===e.gatewayRequestId)return this.logger.debug?.(`[bridge-core] endRun skipped (no pendingPromptId and no currentRunId) sessionId=${e.sessionId} outcome=${t.kind}`),!1;e.mutationTerminalCommitting=!0,this.state.clearStreamIdleTimer(e);const s=e.writeGateToken,i=e.activeRunBinding;let n,o,r,a,l=t;if(s){const e="complete"===l.kind?"cancelled"===l.stopReason?"cancelled":"completed":"interrupted"===l.kind&&"timeout"===l.stopReason?"timeout":"error",t=await this.sessionMutationCoordinator.converge(s,e);t.ok?n=t.permit:l={kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE}}try{const t=resolveTurnId(e),s=t?{turnId:t}:void 0;e.promptDone=!0;const i=e.currentRunId;if("complete"===l.kind){if(i&&this.onRunComplete){const t=e.agentAssistantTextSoFar??e.chatAssistantTextSoFar??"";this.onRunComplete(i,t,l.stopReason)}void 0!==e.pendingPromptId&&this.transport.sendResult(e.pendingPromptId,{stopReason:l.stopReason}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:l.stopReason},s),this.tryGenerateTitleFromHistory(e,e.sessionId)}else"interrupted"===l.kind?(i&&this.onRunFailed&&this.onRunFailed(i,l.message),void 0!==e.pendingPromptId&&this.transport.sendError(e.pendingPromptId,Errors.AgentError.rpcCode,l.message,{code:l.errorCode??"interrupted",stopReason:l.stopReason}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:l.stopReason,error:l.message,...l.errorCode?{errorCode:l.errorCode}:{}},s)):(i&&this.onRunFailed&&this.onRunFailed(i,l.message),void 0!==e.pendingPromptId&&this.transport.sendError(e.pendingPromptId,l.code,l.message),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:"error",error:l.message},s))}catch(e){o=e}try{this.cleanupPrompt(e)}catch(e){r=e}finally{e.writeGateToken=void 0,e.activeRunBinding=void 0,n&&this.sessionMutationCoordinator.release(n)}if(i){const e="complete"===l.kind?"cancelled"===l.stopReason?"cancelled":"completed":"interrupted"===l.kind&&"cancelled"===l.stopReason?"cancelled":"failed";this.activeRuns.markTerminal(i,e)}try{this.onRunEnded?.(e,l)}catch(e){a=e}if(o)throw o;if(r)throw r;if(a)throw a;return!0}adoptBackgroundMutation(e,t){const s=this.sessionMutationCoordinator.attachOrStartExternal({sessionKey:e.gatewaySessionKey,label:"background_run"},"acp",t);return s.ok?("owner"===s.ownership&&(e.writeGateToken=s.token),s):(this.logger.error?.(`[bridge-core] background mutation rejected sessionKey=${e.gatewaySessionKey} runId=${t} reason=${s.reason}`),s)}cleanupPrompt(e){this.state.clearStreamIdleTimer(e);const t=e.currentRunId;if(t){this.state.promptsByRunId.delete(t),this.state.runIdToCronJobId.delete(t),this.state.markRunCompleted(t);const e=`tc_${t}_`;for(const[t,s]of this.state.toolCallIdMap)s.startsWith(e)&&this.state.toolCallIdMap.delete(t)}e.gatewayRequestId&&this.state.promptsByGatewayRequestId.delete(e.gatewayRequestId),e.pendingPromptId=void 0,e.promptDone=void 0,e.currentRunId=void 0,e.gatewayRequestId=void 0,e.assistantStreamStartedAt=void 0,e.gatewayMethod=void 0,e.mutationTerminalCommitting=void 0,e.assistantMq?.close(),e.assistantMq=void 0,e.pendingUserTurnUpdates=void 0}async completePrompt(e,t){"cancelled"!==t?await this.terminateRunOutput(e,{kind:"complete",stopReason:t}):await this.terminateRunOutput(e,{kind:"cancelled",stopReason:"cancelled"})}async interruptPrompt(e,t){await this.terminateRunOutput(e,{kind:"failed",mode:"interrupted",message:t.publicMessage,errorCode:t.errorCode,classification:t})}async failPrompt(e,t,s,i){await this.terminateRunOutput(e,errorRunOutput(t,s,i))}cleanupAllPendingPrompts(){for(const e of this.state.sessions.values())e.writeGateToken?(this.sessionMutationCoordinator.requireRecovery(e.writeGateToken,"gateway_disconnected","disconnect"),this.observeLifecycle(this.terminateRunOutput(e,{kind:"failed",mode:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE}),"gateway_disconnect_recovery",e)):this.observeLifecycle(this.interruptPrompt(e,classifyAgentRuntimeFailure({raw:Errors.GatewayDisconnected.message,source:"live_gateway",runtimeKind:"openclaw"})),"gateway_disconnect_interrupt",e)}handleGatewayResponse(e){const t=e.id;if(!t)return;const s=this.state.promptsByGatewayRequestId.get(t);if(!s||s.promptDone)return;if(!e.ok){const i=e.error,n=i?.message??"gateway returned error";this.logger.warn?.(`[bridge-core] handleGatewayResponse: gateway rejected prompt gatewayRequestId=${t} error=${n}`);const o=classifyAgentRuntimeFailure({raw:n,source:"live_gateway",runtimeKind:"openclaw"});return void("interrupted"===o.kind?this.observeLifecycle(this.interruptPrompt(s,o),"gateway_response_interrupt",s):(this.logRuntimeFailure(s,o),this.observeLifecycle(this.failPrompt(s,Errors.SendFailed.rpcCode,o.publicMessage,o),"gateway_response_failure",s)))}const i=e.payload,n=i?.runId;if(n)s.currentRunId=n,this.sessionMutationCoordinator.bindRun(s.writeGateToken,n),this.observeActiveRun(s),s.assistantStreamStartedAt=Date.now(),this.state.promptsByRunId.set(n,s),openRunOutputRoute(this.runOutputRouter,s,n,["acp"]),this.logger.debug?.(`[bridge-core] handleGatewayResponse: bound runId=${n} gatewayRequestId=${t} sessionId=${s.sessionId}`);else{const e=resolveTurnId(s);e&&openRunOutputRoute(this.runOutputRouter,s,e,["acp"]),this.logger.debug?.(`[bridge-core] handleGatewayResponse: no runId in payload gatewayRequestId=${t} sessionId=${s.sessionId}`)}}async handleCronRequest(e){const t=this.getGateway();if(!t?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);const s=e.method.replace("/","."),i={...e.params??{}};["cron.add","cron.update","cron.remove","cron.run","cron.runs"].includes(s)&&(i.__beeosCronIngress="acp"),"cron.update"===s&&(i.patch=i.patch&&"object"==typeof i.patch?{...i.patch}:{});try{const t=await this.dispatchLegacyCronCapability(s,i),n=await this.mapCronResultForAcp(s,t);this.transport.sendResult(e.id,n??{}),"cron.add"!==s&&"cron.update"!==s&&"cron.remove"!==s||this.refreshCronIsolatedSessionKeys()}catch(t){const i=t instanceof Error?t.message:"cron request failed";this.logger.warn?.(`[bridge-core] ${s} failed: ${i}`),this.transport.sendAcpError(e.id,Errors.SendFailed,{message:i})}}async mapCronResultForAcp(e,t){if(!t||"object"!=typeof t)return t;const s=t;await this.cronIdentities.snapshot();const i=e=>{if(!e||"object"!=typeof e)return e;const t=e,s=t.id;if("string"!=typeof s)return e;const i=this.cronIdentities.localId(s)??s,n=this.cronChannelCache.get(i),o=n?encodeBeeosChannelTag("string"==typeof t.description?t.description:void 0,n):t.description;return{...t,id:i,...void 0===o?{}:{description:o}}};if(Array.isArray(s.jobs))return{...s,jobs:s.jobs.map(i)};if(s.jobs&&"object"==typeof s.jobs){const e=s.jobs,t={};for(const[s,n]of Object.entries(e))t[s]=i(n);return{...s,jobs:t}}return Array.isArray(s.runs)?{...s,runs:s.runs.map(e=>{if(!e||"object"!=typeof e)return e;const t=e,s="string"==typeof t.jobId?this.cronIdentities.localId(t.jobId)??t.jobId:t.jobId;return{...t,jobId:s}})}:"cron.add"===e||"cron.update"===e?i(t):t}async allocateCronChannel(e,t){if(!this.cloud)return void this.logger.debug?.("[bridge-core] Cloud gateway unavailable, skip channel alloc");const s=this.getAuthoritativeInstanceId?.()?.trim();if(!s?.startsWith("inst_"))return void this.logger.warn?.("[bridge-core] no authoritative inst_* instance id resolved yet, skip channel alloc");const i=this.resolvePlatformAgentId(t)?.trim();if(!i)return void this.logger.warn?.(`[bridge-core] no platform agent id for runtimeLocalKey=${t}, skip channel alloc`);const n={source:"cron",job_name:e,protocol:"beeos-conversation",target_agent_id:i,title:e};try{return(await this.cloud.channels.create({participants:[s,"service:cron"],metadata:n})).id}catch(e){return void this.logger.warn?.(`[bridge-core] channel alloc error: ${e instanceof Error?e.message:e}`)}}setCronChannelCache(e,t){if(this.cronChannelCache.size>=AcpGatewayBridgeCore.CRON_CHANNEL_CACHE_MAX){const e=this.cronChannelCache.keys().next().value;void 0!==e&&this.cronChannelCache.delete(e)}this.cronChannelCache.set(e,t)}async refreshCronIsolatedSessionKeys(){const e=this.getGateway();if(e?.isConnected)try{const t=await e.request("cron.list",{}),s=t?.jobs;if(!Array.isArray(s))return;const i=new Set,n=[];for(const e of s){if(!e||"object"!=typeof e)continue;const t=e;if("string"==typeof t.id&&n.push(t.id),"isolated"!==t.sessionTarget)continue;const s=t.sessionKey;"string"==typeof s&&s.length>0&&i.add(s)}this.cronIsolatedSessionKeys=i,await this.cronIdentities.reconcile(n)}catch(e){this.logger.debug?.(`[bridge-core] refreshCronIsolatedSessionKeys failed: ${e instanceof Error?e.message:e}`)}}}async function fetchWithRetry(e,t={}){const s=t.maxRetries??3;let i;for(let n=0;n<=s;n++){const o=await fetch(e,{signal:AbortSignal.timeout(12e4),redirect:"follow"});if(o.ok)return Buffer.from(await o.arrayBuffer());if(429===o.status||o.status>=500){const r=Number(o.headers.get("retry-after"))||0,a=1e3*Math.max(r,2**n);if(i=new Error(`HTTP ${o.status} from ${e}`),n<s){t.logger?.info?.(`[bridge-core] Download ${o.status}, retry ${n+1}/${s} in ${(a/1e3).toFixed(0)}s`),await new Promise(e=>setTimeout(e,a));continue}}throw new Error(`Failed to download ${e}: HTTP ${o.status}`)}throw i??new Error(`Download failed after retries: ${e}`)}const SKILL_ROOT_MARKERS=["SKILL.md","skill.md","SKILL.MD"];function normalizeSkillArtifactPath(e){const t=e.replaceAll("\\","/").replace(/^\.\//,"");if(!t||t.startsWith("/")||t.split("/").includes(".."))throw new RuntimeCommandError("INVALID_PARAMS",`Invalid skill artifact path: ${e}`);return t}function parseSkillZip(e){if(e.byteLength>MAX_ZIP_BYTES)throw new RuntimeCommandError("INVALID_PARAMS","Skill archive too large");const t=new AdmZip(e).getEntries().filter(e=>!e.isDirectory).map(e=>({name:normalizeSkillArtifactPath(e.entryName),bytes:e.getData()})).filter(e=>!e.name.split("/").includes("__MACOSX"));if(0===t.length)throw new RuntimeCommandError("INVALID_PARAMS","Skill archive is empty");const s=1===new Set(t.map(e=>e.name.split("/")[0])).size&&t.every(e=>e.name.includes("/")),i=new Map;for(const e of t){const t=s?e.name.slice(e.name.indexOf("/")+1):e.name;if(!t||i.has(t))throw new RuntimeCommandError("INVALID_PARAMS",`Duplicate skill path: ${t}`);i.set(t,e.bytes)}return i}async function resolveSkillRoot(e){for(const t of SKILL_ROOT_MARKERS)try{return await stat(join(e,t)),e}catch{}const t=(await readdir(e,{withFileTypes:!0})).filter(e=>e.isDirectory()&&!e.name.startsWith(".")&&"__MACOSX"!==e.name);if(1===t.length){const s=join(e,t[0].name);for(const e of SKILL_ROOT_MARKERS)try{return await stat(join(s,e)),s}catch{}return s}return e}async function extractZipToDir(e,t){const s=new AdmZip(e),i=join(t,".beeos-extract-tmp-"+Date.now());try{await mkdir(i,{recursive:!0}),s.extractAllTo(i,!0);const e=await resolveSkillRoot(i),n=await readdir(e,{withFileTypes:!0});let o=0;for(const s of n){if(s.name.startsWith(".beeos-extract-tmp")||"__MACOSX"===s.name)continue;const i=join(e,s.name),n=join(t,s.name);await rename(i,n).catch(async()=>{await cp(i,n,{recursive:!0})}),o++}if(0===o)throw new Error("Zip archive contained no extractable files")}finally{await rm(i,{recursive:!0,force:!0}).catch(()=>{})}}const VALID_SLUG_PATTERN=/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,NON_ASCII_PATTERN=/[^\x00-\x7F]/;function validateSkillSlug(e){const t=e.trim();if(!t||t.includes("/")||t.includes("\\")||t.includes(".."))throw new Error(`Invalid skill slug: ${e}`);if(NON_ASCII_PATTERN.test(t)||!VALID_SLUG_PATTERN.test(t))throw new Error(`Invalid skill slug: ${e}`);return t}function safeDirName(e){const t=e.trim();return t?t.replaceAll("/","__").replaceAll("\\","__"):t}function extractMcpCredentialMaterial(e){const t=mcpStringMap(e.headers,"MCP headers"),s=mcpStringMap(e.env,"MCP environment");if(!(t&&0!==Object.keys(t).length||s&&0!==Object.keys(s).length))return;const i=e.transport??("string"==typeof e.command?"stdio":"string"==typeof e.url?"streamable-http":void 0);if("stdio"===i&&t&&Object.keys(t).length)throw new Error("MCP stdio config cannot include headers");if("stdio"!==i&&s&&Object.keys(s).length)throw new Error("MCP HTTP config cannot include env");return{...t&&Object.keys(t).length?{headers:t}:{},...s&&Object.keys(s).length?{env:s}:{}}}function mcpStringMap(e,t){if(void 0!==e){if(!e||"object"!=typeof e||Array.isArray(e)||Object.entries(e).some(([e,t])=>!e||/[\u0000-\u001f\u007f]/.test(e)||"string"!=typeof t))throw new Error(`${t} invalid`);return{...e}}}function requireCredentialReferencePort(e){if(!e)throw new Error("MCP credential reference vault is unavailable");return e}function resolveSkillInstallDir(e,t){const s=safeDirName(t),i=join(e,s),n=resolve(e),o=resolve(i),r=relative(n,o);if(!r||".."===r||r.startsWith(`..${sep}`)||path.isAbsolute(r))throw new Error("Invalid skill target path");return i}
1
+ import{randomUUID,createHash}from"node:crypto";import{mkdir,readFile,rm,stat,readdir,rename,cp}from"node:fs/promises";import path from"node:path";import{join,resolve,relative,sep}from"node:path";import{homedir}from"node:os";import AdmZip from"adm-zip";import{classifyCatalogSessionKey,prepareOpenClawToolCatalog}from"../runtime/prepare-openclaw-tool-catalog.js";import{sanitizePromptPayload}from"../message-sanitization/prompt-payload-sanitizer.js";import{previewSanitizedText,stripTransportMetadata}from"../message-sanitization/transport-metadata-sanitizer.js";import{isHeartbeatPromptText,isCronPromptText,extractCronJobId,classifyHeartbeatFilter}from"../message-sanitization/message-classifier.js";import{createCronIdentityStore,createInMemoryCronIdentityStore}from"../cron-identities.js";import{CANVAS_UPDATE_HINT,buildActiveSurfacesContext}from"../canvas/tools.js";import{parseMentionsParam,buildMentionInstruction}from"../delegation/mention-resolver.js";import{EXTERNAL_PROTOCOL_TAGS}from"./external-protocol-tags.js";import{extractPromptBlocks,toGatewayPrompt,resolveSlashCommandName as resolveSlashCmd}from"./prompt-converter.js";import{extractBeeosUserTextFromHistoryContent}from"./beeos-user-envelope.js";import{loadHistory,loadHistoryPage,groupIntoTurns,splitUserReplayBlocks}from"./history-replay.js";import{classifyHistoricalAssistantTurnCompletion}from"./history-turn-completion-policy.js";import{LocalSessionHistory}from"./local-session-history.js";import{reconcileMissingArtifacts}from"./missing-artifacts-reconciler.js";import{AcpRunOutputChannel}from"./acp-run-output-channel.js";import{buildResolutionPlan,resolveResources,applyResolutions}from"./prompt-resource-resolver.js";import{readEnrichmentCache,readUploadToolResult}from"../upload/result-cache.js";import{buildToolResultSessionContentBlocks}from"./tool-result-blocks.js";import{prefersChatFileArtifacts}from"./file-artifact-presenter.js";import{buildToolCallPayload,buildToolCallUpdatePayload}from"./tool-call-payload-builder.js";import{Errors}from"../errors/index.js";import{classifyAgentRuntimeFailure,INTERRUPTED_PUBLIC_MESSAGE}from"../runtime/agent-runtime-failure-classifier.js";import{isPlaceholderTitle}from"../runtime/conversation-title.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"../runtime/session-mutation-coordinator.js";import{observeLifecyclePromise}from"../runtime/lifecycle-promise-observer.js";import{RunOutputRouter,errorRunOutput}from"../runtime/run-output-router.js";import{AgentMappingRegistry}from"../runtime/agent-mapping-registry.js";import{RuntimeSessionNewRejectedError}from"../runtime/runtime-rpc-consumer.js";import{normalizeRuntimeAgentLocalKey,OpenClawRuntimeCommandDispatcher,RuntimeCommandError}from"../runtime/runtime-command-dispatcher.js";import{FileRuntimeAgentIdentityRegistry}from"../runtime/runtime-agent-identity-registry.js";import{FileRuntimeLeasePort}from"../runtime/runtime-operation-lease.js";import{FileRuntimeCommandJournal,InMemoryRuntimeCommandJournal}from"../runtime/runtime-command-journal.js";import{OpenClawRuntimeCapabilities}from"../runtime/openclaw-runtime-capabilities.js";import{RuntimeCanvasService}from"../canvas/runtime-canvas-service.js";import{RuntimeSkillsService}from"../runtime/runtime-skills-service.js";import{RuntimeCronService,encodeBeeosChannelTag}from"../runtime/runtime-cron-service.js";export{BEEOS_CHANNEL_TAG_RE,decodeBeeosChannelId,encodeBeeosChannelTag,stripBeeosChannelTag}from"../runtime/runtime-cron-service.js";import{openClawKeyMatchesConversation,openClawSessionKeysFromList,resolveLiveOpenClawSessionKey}from"../runtime/openclaw-session-key-resolution.js";import{LocalConversationHistoryBoundary,OpenClawGatewaySessionMetadataPort,OpenClawSessionMetadataService,RuntimeSessionControlService}from"../runtime/runtime-session-control-service.js";import{pageRuntimeSessions,parseRuntimeSessionListPage}from"../runtime/runtime-session-list-policy.js";import{assertTrustedCoreExecutionAttempt,createAcpCoreExecutionAttempt}from"../runtime/core-execution-attempt-context.js";import{CompositeActiveRunPort}from"../runtime/composite-active-run-port.js";import{resolveChannelFromSessionKey}from"../channel-detection.js";import{resolveTurnId,recordTurnIdForRole,recordToolCallTurnForUpdate}from"./gateway-events.js";import{emitToolResultProjectionForTurn}from"./tool-result-emitter.js";import{extractPersistedToolResultEnvelope,findPersistedToolResultEnvelopeByMessageId}from"./tool-result-transcript.js";import{projectPersistedToolResultForReplay}from"./tool-result-projection.js";import{openRunOutputRoute}from"./run-output-route.js";import{McpService}from"./mcp-service.js";import{McpDesiredStateStore,normalizeMcpPublicServerConfig}from"./mcp-desired-state-store.js";import{requireSecretFreeMcpServerName}from"./mcp-public-config-security.js";import{McpDesiredStateController}from"./mcp-desired-state-controller.js";import{CredentialReferenceRuntimeResolver}from"./mcp-credential-reference-port.js";import{ACP_PROTOCOL_VERSION}from"@beeos-ai/beeos-types/acp";import{canonicalizeJcs}from"@beeos-ai/beeos-types/runtime";import{retainedVerifiedNativePrompt}from"../runtime/native-prompt-authority.js";import{MAX_ZIP_BYTES,parseAndValidateTemplatePackage,TemplatePackageError}from"./agent-template-package.js";const DEFAULT_AGENT_ID="main";export function normalizeAgentId(e){return normalizeRuntimeAgentLocalKey(e)}function narrowContentBlock(e){switch(e.type){case"text":return"string"==typeof e.text?{type:"text",text:e.text}:void 0;case"resource_link":return"string"==typeof e.uri?{type:"resource_link",uri:e.uri,...void 0!==e.name?{name:e.name}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}}:void 0;case"file":return{type:"file",...void 0!==e.text?{text:e.text}:{},...void 0!==e.uri?{uri:e.uri}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}};case"image":return{type:"image",...void 0!==e.uri?{uri:e.uri}:{},...void 0!==e.mimeType?{mimeType:e.mimeType}:{}};default:return}}const DEFAULT_MODE={id:"default",name:"Default",description:"Default agent mode"},USER_MESSAGE_PREFIX="User Message From BeeOS:",USER_MESSAGE_PREFIX_WITH_NEWLINE=`${USER_MESSAGE_PREFIX}\n`,SLASH_COMMAND_NAME_RE=/^[a-z0-9_-]+$/i,HISTORY_PAGE_DEFAULT_LIMIT=100,HISTORY_PAGE_MAX_LIMIT=1e3;function withUserMessagePrefix(e){return e.startsWith(USER_MESSAGE_PREFIX)?e:`${USER_MESSAGE_PREFIX_WITH_NEWLINE}${e}`}const BACKGROUND_SESSION_PATTERNS=[":cron:",":hook:","node-",...EXTERNAL_PROTOCOL_TAGS];function isBackgroundSessionKey(e){return BACKGROUND_SESSION_PATTERNS.some(t=>e.includes(t))}function isSystemDefaultSessionKey(e){const t=e.split(":");if(t.length<3||"agent"!==t[0])return!1;const s=t.slice(2).join(":");return"main"===s||s===t[1]}function cleanGatewayDerivedTitle(e){if(!e)return;let t=extractBeeosUserTextFromHistoryContent(e).text;return/^\[System\b/.test(t)?void 0:(t=t.trim(),t||void 0)}function cleanHistoryUserContent(e){return extractBeeosUserTextFromHistoryContent(e).text.trim()}function mergePersistedToolResultEnvelopes(e,t){if(e||t)return{toolName:e?.toolName||t?.toolName||"tool",...e?.toolCallId||t?.toolCallId?{toolCallId:e?.toolCallId??t?.toolCallId}:{},...e?.rawText??t?.rawText?{rawText:e?.rawText??t?.rawText}:{},...void 0!==e?.rawResult||void 0!==t?.rawResult?{rawResult:e?.rawResult??t?.rawResult}:{},...e?.timestamp||t?.timestamp?{timestamp:e?.timestamp??t?.timestamp}:{}}}function resolveStandaloneSlashCommandName(e,t){if(0===e.length)return;if(e.some(e=>"text"!==e.type))return;const s=t.trim();if(!s||s.includes("\n")||!s.startsWith("/"))return;const i=s.match(/^\/([^\s:]+)(?::|\s|$)/)?.[1]?.trim();return i&&!i.includes("/")&&SLASH_COMMAND_NAME_RE.test(i)?i.toLowerCase():void 0}class AcpRuntimeCommandResourceStore{fetchPackage;parsePackage;isValidationError;content=new Map;artifacts=new Map;mcpDefinitions=new Map;constructor(e,t,s){this.fetchPackage=e,this.parsePackage=t,this.isValidationError=s}registerContent(e){const t=`acp-content:sha256:${createHash("sha256").update(e).digest("hex")}`;return this.content.set(t,e),t}registerMcpDefinition(e,t){this.mcpDefinitions.set(JSON.stringify(e),structuredClone(t))}async resolveMcpDefinition(e){const t=this.mcpDefinitions.get(JSON.stringify(e));if(!t)throw new Error("ACP MCP definition was not verified for this request");return structuredClone(t)}async registerArtifact(e,t){try{const s=await this.fetchPackage(e);if(!s.ok)throw new TemplatePackageError(`failed to download package: HTTP ${s.status}`);const i=Buffer.from(await s.arrayBuffer());if(i.length>MAX_ZIP_BYTES)throw new TemplatePackageError(`package too large: ${i.length} bytes (max ${MAX_ZIP_BYTES})`);const n=createHash("sha256").update(i).digest("hex");if(t?.trim()&&t.trim().toLowerCase()!==n)throw new TemplatePackageError("package sha256 mismatch");const o=this.parsePackage(i),r=`acp-artifact:sha256:${n}`;return this.artifacts.set(r,o),{artifactRef:r,version:o.manifest?.version??"0.0.0",sha256:n,sizeBytes:String(i.length),source:"acp-verified"}}catch(e){if(e instanceof TemplatePackageError||this.isValidationError(e))throw new RuntimeCommandError("INVALID_PARAMS",`Invalid template package: ${e instanceof Error?e.message:String(e)}`);throw new RuntimeCommandError("EXECUTION_FAILED",`Apply template failed: ${e instanceof Error?e.message:String(e)}`)}}registerParsedArtifact(e){const t=createHash("sha256");for(const[s,i]of[...e.workspaceFiles].sort(([e],[t])=>e.localeCompare(t)))t.update(s).update("\0").update(i).update("\0");const s=t.digest("hex"),i=`acp-artifact:sha256:${s}`;this.artifacts.set(i,e);const n=[...e.workspaceFiles.values()].reduce((e,t)=>e+t.byteLength,0);return{artifactRef:i,version:e.manifest?.version??"0.0.0",sha256:s,sizeBytes:String(n),source:"acp-verified"}}async readContent(e){const t=this.content.get(e);if(void 0===t)throw new Error(`unknown contentRef: ${e}`);return t}async readVerifiedArtifact(e){const t=this.artifacts.get(e.artifactRef);if(!t)throw new Error(`unknown verified artifact: ${e.artifactRef}`);return t}}export const lateBoundRuntimeAgentProjectionPort=e=>({prepareCommittedMutation:async t=>{const s=e();if(!s)throw new Error("agent projection production is unavailable");return await s.prepareCommittedMutation(t)},applyCommittedMutation:async t=>{const s=e();if(!s)throw new Error("agent projection production is unavailable");return await s.applyCommittedMutation(t)},compactCommittedMutation:async t=>{const s=e();if(!s)throw new Error("agent projection production is unavailable");await s.compactCommittedMutation(t)}});function deriveSessionId(e,t){const s=createHash("sha256").update(`${t}:${e}`).digest("hex").substring(0,32);return`session:${s.slice(0,8)}-${s.slice(8,12)}-${s.slice(12,16)}-${s.slice(16,20)}-${s.slice(20,32)}`}function parseAgentIdFromKey(e){const t=e.split(":");return(t.length>=3&&"agent"===t[0]?t[1]:"")||"main"}function createSessionObject(e){const t=parseAgentIdFromKey(e.gatewayKey);return{sessionId:deriveSessionId(e.gatewayKey,e.instanceId),gatewaySessionKey:e.gatewayKey,agentId:t,platformAgentId:e.resolvePlatformAgentId?.(t),createdAt:Date.now(),chatReplayDedupKeys:new Set,cwd:e.cwd,title:e.title,channelType:resolveChannelFromSessionKey(e.gatewayKey,e.thirdPartyChannels)}}class HistoryUpdateRecorder{transport;updates=[];constructor(e){this.transport=e}sendSessionUpdate(e,t,s){const i=this.transport.materializeSessionUpdate(t,s);return null!==i&&this.updates.push(i),!0}}export class AcpGatewayBridgeCore{state;transport;config;logger;writeObs;getGateway;thirdPartyChannels;beeosFileResolver;cloud;triggerAgentSync;sessionMutationCoordinator;runtimeCommands;runtimeAgentIdentities;runtimeLeases;runtimeCommandResources;localHistoryBoundary;activeRuns;markAgentProjectionDirty;projectionObservationTimeoutMs;cronChannelCache=new Map;static CRON_CHANNEL_CACHE_MAX=256;localSessionHistory;mcp;catalogPrepareInFlight=new Set;runtimeTerminalService;runOutputRouter;agentMappings;initialized=!1;get agentMapping(){return this.agentMappings.snapshot()}cronIsolatedSessionKeys=new Set;cronIdentities;cronReady;sessionPreloadInFlight=null;sessionPreloadInFlightGeneration=null;sessionPreloadReady=Promise.resolve();resolveSessionPreloadReady=null;rejectSessionPreloadReady=null;sessionPreloadGeneration=0;sessionPreloadBarrierActive=!1;sessionPreloadFailed=!1;async setAgentMapping(e){await this.agentMappings.replace(e)}async applyAgentMapping(e){for(const t of this.state.sessions.values())!t.platformAgentId&&t.agentId&&(t.platformAgentId=e.get(t.agentId)?.id);this.sessionPreloadFailed&&this.beginGatewaySessionPreload(),this.sessionPreloadBarrierActive&&await this.preloadSessionsFromGateway()}beginGatewaySessionPreload(){this.sessionPreloadBarrierActive&&this.sessionPreloadReady.catch(()=>{}),this.sessionPreloadGeneration+=1,this.sessionPreloadBarrierActive=!0,this.sessionPreloadFailed=!1,this.sessionPreloadReady=new Promise((e,t)=>{this.resolveSessionPreloadReady=e,this.rejectSessionPreloadReady=t}),this.sessionPreloadReady.catch(()=>{})}failGatewaySessionPreload(){this.sessionPreloadBarrierActive&&(this.sessionPreloadBarrierActive=!1,this.sessionPreloadFailed=!0,this.rejectSessionPreloadReady?.(new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway disconnected before session hydration completed")),this.resolveSessionPreloadReady=null,this.rejectSessionPreloadReady=null)}async waitForGatewaySessionPreload(){await this.sessionPreloadReady}onceAgentMappingReady(e){this.agentMappings.onceReady(e)}onAgentMappingUpdated(e){const t=this.agentMappings.snapshot();t&&t.size>0&&e(new Map(t)),this.agentMappings.subscribe(t=>e(new Map(t)))}markAgentSyncDone(){this.agentMappings.markSynchronized()}waitForAgentMapping(e=15e3){return this.agentMappings.waitForSynchronization(e)}handleTerminalRequest=null;handleTerminalNotification=null;onRunComplete=null;onRunFailed=null;onRunEnded=null;handleSlashCommand=null;onPromptStart=null;instanceId;getAuthoritativeInstanceId;mcpCredentialReferences;constructor(e){this.state=e.state,this.transport=e.transport,this.config=e.config,this.logger=e.logger,this.writeObs=e.writeObs,this.getGateway=e.getGateway,this.instanceId=e.instanceId||"unknown",this.getAuthoritativeInstanceId=e.getAuthoritativeInstanceId,this.mcpCredentialReferences=e.mcpCredentialReferences,this.thirdPartyChannels=e.thirdPartyChannels??new Set,this.beeosFileResolver=e.beeosFileResolver,this.cloud=e.cloud,this.triggerAgentSync=e.triggerAgentSync,this.markAgentProjectionDirty=e.markAgentProjectionDirty,this.projectionObservationTimeoutMs=e.projectionObservationTimeoutMs??5e3,this.sessionMutationCoordinator=e.sessionMutationCoordinator,this.agentMappings=e.agentMappings??new AgentMappingRegistry,this.agentMappings.subscribe(e=>this.applyAgentMapping(e)),this.runOutputRouter=e.runOutputRouter??new RunOutputRouter([new AcpRunOutputChannel({state:this.state,transport:this.transport})],{lifecycle:{terminal:(e,t)=>this.commitRunOutputTerminal(e,t)}}),this.runtimeAgentIdentities=e.runtimeAgentIdentities??new FileRuntimeAgentIdentityRegistry(join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","runtime-agent-identities.json")),this.runtimeLeases=e.runtimeLeases??new FileRuntimeLeasePort(join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","runtime-operation-leases.json")),this.runtimeCommandResources=e.runtimeCommandResources??new AcpRuntimeCommandResourceStore((t,s)=>e.cloud&&new URL(t).origin===e.cloud.baseUrl.origin?e.cloud.asFetch()(t,s):fetch(t,s),e.runtimeTemplatePackageParser?.parse??parseAndValidateTemplatePackage,e.runtimeTemplatePackageParser?.isValidationError??(e=>e instanceof TemplatePackageError));const t=e.runtimeCommandJournal??(e.runtimeLeases||process.env.VITEST?new InMemoryRuntimeCommandJournal:new FileRuntimeCommandJournal(join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","runtime-command-journal.json"),void 0,void 0,{onMaintenanceError:e=>this.logger.error?.(`[runtime-journal] event=checkpoint_failed trigger=threshold error=${e instanceof Error?e.message:String(e)}`)}));this.localSessionHistory=new LocalSessionHistory({logger:this.logger}),this.cronIdentities=e.cronIdentities??(process.env.VITEST?createInMemoryCronIdentityStore():createCronIdentityStore(this.logger)),this.cronReady=this.cronIdentities.initialize(),this.cronReady.catch(()=>{});const s=e.mcpCredentialReferences?new CredentialReferenceRuntimeResolver(e.mcpCredentialReferences):void 0,i=e.mcpProjection?new McpDesiredStateController(new McpDesiredStateStore(e.mcpDesiredStatePath??join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos","mcp-desired-state.json")),e.mcpProjection,s,e.mcpCredentialReferences,t,e.runtimeReceiptHistory,this.logger):void 0;this.mcp=new McpService({transport:this.transport,logger:this.logger,state:this.state,...i?{desiredState:i}:{}}),this.localHistoryBoundary=new LocalConversationHistoryBoundary(e=>{const t=this.state.sessions.get(e);t&&(t.chatReplayDedupKeys.clear(),this.onSessionCleared?.(t.sessionId,t.gatewaySessionKey))}),this.activeRuns=e.activeRuns??new CompositeActiveRunPort;const n=new RuntimeCronService({getGateway:e.getGateway,identities:this.cronIdentities,channels:{allocate:async(e,t)=>await this.allocateCronChannel(e,t),lookup:e=>this.cronChannelCache.get(e),remember:(e,t)=>this.setCronChannelCache(e,t),forget:e=>{this.cronChannelCache.delete(e)}}}),o=new RuntimeSessionControlService({convergeRun:async({runtimeSessionKey:e})=>{const t=this.state.sessionsByGatewayKey.get(e),s=(new Date).toISOString();if(!t||t.promptDone&&void 0===t.pendingPromptId&&void 0===t.currentRunId)return{state:"no_active_run",quiescence:{state:"verified",observedAt:s}};const i=this.getGateway();if(!i?.isConnected)return{state:"recovery_required",reason:"gateway_disconnected"};if(!t.writeGateToken||!t.currentRunId)return{state:"recovery_required",reason:"run_conflict",runtimeRunId:t.currentRunId??"unknown"};const n=t.currentRunId,o=await this.sessionMutationCoordinator.requestCancel(t.writeGateToken,i);if(!o.ok)return{state:"recovery_required",reason:"terminal_not_observed",runtimeRunId:n};const r=await o.terminalObservation;return r.ok?{state:"terminal_observed",runtimeRunId:n,abortObservation:o.acknowledged?{state:"target_acknowledged",aborted:!0,runIds:[n],targetRunAcknowledged:!0}:{state:"already_stopped",aborted:!1,runIds:[]},terminalState:"cancelled",quiescence:{state:"verified",observedAt:(new Date).toISOString()}}:{state:"recovery_required",reason:"quiescence_failed"===r.recovery.reason?"quiescence_failed":"quiescence_timeout"===r.recovery.reason?"quiescence_timeout":"terminal_not_observed",runtimeRunId:n}},reset:async e=>{const t=this.getGateway();if(!t?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected");await t.sessionsReset(e)}},new OpenClawSessionMetadataService(new OpenClawGatewaySessionMetadataPort(e.getGateway,(e,t)=>{const s=this.state.sessionsByGatewayKey.get(e);if(!s)throw new RuntimeCommandError("INVALID_PARAMS",`setModel projection requires a live local session for key=${e}`);void 0===t.modelOverrideId?delete s.modelOverrideId:s.modelOverrideId=t.modelOverrideId,s.effectiveModelId=t.effectiveModelId,void 0===t.modelProvider?delete s.modelProvider:s.modelProvider=t.modelProvider},{resolveLiveKey:e=>resolveLiveOpenClawSessionKey(e,this.state.sessions.values()),waitForHydration:async()=>await this.waitForGatewaySessionPreload(),reconcileMissingLiveKey:async(e,t)=>await this.reconcileMissingGatewaySession(e,t),materializeMissingModelKey:async(e,t)=>await this.materializeMissingModelSessionKey(e,t),registerMaterializedModelKey:e=>this.registerMaterializedModelSession(e)},this.sessionMutationCoordinator)),this.activeRuns),r=e.getAgentProjection?lateBoundRuntimeAgentProjectionPort(e.getAgentProjection):void 0,a=e.runtimeCommandDispatcher??new OpenClawRuntimeCommandDispatcher({getGateway:e.getGateway,resources:this.runtimeCommandResources,identities:this.runtimeAgentIdentities,leases:this.runtimeLeases,journal:t,logger:e.logger,...r?{projection:r}:{},...e.templateOriginReporter?{templateOriginReporter:e.templateOriginReporter}:{},...e.runExclusive?{runExclusive:e.runExclusive}:{}});this.runtimeCommands=a,a instanceof OpenClawRuntimeCommandDispatcher&&a.setCapabilities(new OpenClawRuntimeCapabilities({getGateway:e.getGateway,identities:this.runtimeAgentIdentities,sessions:o,skills:new RuntimeSkillsService({getGateway:e.getGateway,resources:this.runtimeCommandResources,getOpenclawHome:()=>this.getOpenclawHome()}),mcp:this.mcp,terminal:{execute:async(e,t)=>{if(!this.runtimeTerminalService)throw new RuntimeCommandError("INVALID_PARAMS","terminal canonical delegate is not configured");return await this.runtimeTerminalService.execute(e,t)}},canvas:new RuntimeCanvasService({apply:async(e,t)=>{await this.applyCanonicalCanvasControl(e,t)}}),cron:n})),this.transport.setSessionUpdateObserver((e,t,s)=>{const i=s?.turnId;if(!i)return;const n=this.state.sessions.get(e);n&&(recordTurnIdForRole(n,t,i),recordToolCallTurnForUpdate(n,t,i))})}setRuntimeTerminalService(e){this.runtimeTerminalService=e}setRuntimeReceiptHistoryPort(e){this.mcp.setRuntimeReceiptHistoryPort(e)}getRuntimeCommandDispatcher(){return this.runtimeCommands}async startMcp(){await this.mcp.start()}async stopMcp(){await this.mcp.stop()}nudgeMcpNativeProjection(){this.mcp.nudgeNativeProjection()}observeActiveRun(e){const t=e.currentRunId,s=e.writeGateToken,i=this.sessionMutationCoordinator.snapshot(e.gatewaySessionKey),n=void 0===e.pendingPromptId?e.gatewayRequestId??t:String(e.pendingPromptId);if(!t||!i||!n)return;const o={runtimeConversationUid:e.sessionId,runtimeSessionKey:e.gatewaySessionKey,historyGeneration:this.localHistoryBoundary.currentGeneration(e.sessionId),targetMessageId:n,runtimeRunId:t,activeRunRegistrationId:`acp:${i.operationId}`};return this.activeRuns.register(o,async()=>{const n=this.getGateway();if(!n?.isConnected||e.currentRunId!==t)return"recovery_required";const o=s?.operationId===i.operationId?await this.sessionMutationCoordinator.requestCancel(s,n):await this.sessionMutationCoordinator.requestCancelForSession(e.gatewaySessionKey,n);return o.ok?"accepted":!("reason"in o)||"terminal_already_observed"!==o.reason&&"no_active_mutation"!==o.reason?"recovery_required":"already_terminal"}),e.activeRunBinding=o,o}getActiveRunAuthority(){return{activeRuns:this.activeRuns,historyGeneration:e=>this.localHistoryBoundary.currentGeneration(e)}}resolvePlatformAgentId(e){return this.agentMapping?.get(e)?.id}resolveLocalAgentId(e){if(this.agentMapping&&e)for(const[t,s]of this.agentMapping)if(s.id===e)return t}getDefaultAgentId(){return this.agentMapping&&this.agentMapping.size>0?this.agentMapping.keys().next().value??"main":"main"}registerSession(e,t={}){const{ensureVerbose:s=!0}=t,i=(this.state.sessions.has(e.sessionId),this.state.sessionsByGatewayKey.get(e.gatewaySessionKey));!0===i?.canvasActive&&(e.canvasActive=!0),this.state.sessions.set(e.sessionId,e),this.state.sessionsByGatewayKey.set(e.gatewaySessionKey,e),s&&this.ensureVerboseFull(e),this.subscribeSessionMessages(e)}async ensureVerboseFull(e){if(e.verbosePatched)return;const t=this.getGateway();if(t?.isConnected){e.verbosePatched=!0;try{await t.sessionsPatch(e.gatewaySessionKey,{verboseLevel:"full"}),await this.scheduleSessionToolCatalog(e.agentId,e.gatewaySessionKey)}catch(t){e.verbosePatched=!1,this.logger.debug?.(`[bridge-core] ensureVerboseFull failed: ${t instanceof Error?t.message:t}`)}}}async resolveNativePromptSessionKey(e,t){const s=retainedVerifiedNativePrompt(e);if(!s||!this.runtimeAgentIdentities.resolveActiveLocalKey)throw new RuntimeCommandError("INVALID_PARAMS","native prompt target authority unavailable");t();const i=await this.runtimeAgentIdentities.resolveActiveLocalKey(s.runtimeAgentUid);if(t(),this.resolveLocalAgentId(s.params.platformAgentId)!==i)throw new RuntimeCommandError("INVALID_PARAMS","native prompt target does not match platform projection");const n=`agent:${i}:beeos-conversation:ctx-${s.command.context.conversation.runtimeConversationUid}`,o=this.state.findSessionByKey(n);if(o&&(o.agentId!==i||void 0!==o.platformAgentId&&o.platformAgentId!==s.params.platformAgentId))throw new RuntimeCommandError("INVALID_PARAMS","native prompt session belongs to another target");return n}async prepareNativePromptModel(e,t,s){const i=retainedVerifiedNativePrompt(e);if(!i)throw new RuntimeCommandError("INVALID_PARAMS","native model proof unavailable");const n=async()=>{s();const i=await this.resolveNativePromptSessionKey(e,s);s();const n=this.sessionMutationCoordinator.snapshot(i);if(i!==t.sessionKey||n?.operationId!==t.operationId)throw new RuntimeCommandError("OUTCOME_UNKNOWN","native model preparation lost its physical session gate")};await n();const o=this.getGateway();if(!o?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected");const r=await o.sessionsList();await n();const a=i.command.context.conversation.runtimeConversationUid,l=(e,s)=>{const i=[...openClawSessionKeysFromList(e)].filter(e=>openClawKeyMatchesConversation(e,a));if(i.some(e=>e!==t.sessionKey)||s&&!i.includes(t.sessionKey))throw new RuntimeCommandError("OUTCOME_UNKNOWN","native model session conflicts with authoritative target")};l(r,!1);const d=new OpenClawGatewaySessionMetadataPort(()=>this.getGateway()),c=await d.prepareNativeModel(t.sessionKey,i.params.modelOverrideId,n,i.params.mode);await n();const m=await o.sessionsList();await n(),l(m,!0),this.registerMaterializedModelSession(t.sessionKey);const u=this.state.findSessionByKey(t.sessionKey);void 0===c.modelOverrideId?delete u.modelOverrideId:u.modelOverrideId=c.modelOverrideId,u.effectiveModelId=c.effectiveModelId,void 0===c.modelProvider?delete u.modelProvider:u.modelProvider=c.modelProvider,u.channelId=i.command.context.conversation.runtimeConversationUid,u.inboundMessageId=i.params.taskRequestMessageId,u.activeMentions=void 0,this.state.lastActiveSessionId=u.sessionId,this.onSessionLoaded?.(u.sessionId,u.gatewaySessionKey,i.params.platformAgentId),await this.ensureVerboseFull(u),void 0!==i.params.reasoning&&await o.sessionsPatch(u.gatewaySessionKey,{thinkingLevel:i.params.reasoning.reasoningOverrideId}),this.onPromptStart?.()}async applyImTurnModel(e,t){const s=new OpenClawGatewaySessionMetadataPort(()=>this.getGateway());await s.prepareNativeModel(e,t,async()=>{})}async restoreImTurnModel(e){const t=new OpenClawGatewaySessionMetadataPort(()=>this.getGateway());await t.prepareNativeModel(e,void 0,async()=>{})}ensureSessionForKey(e){const t=this.state.findSessionByKey(e);if(t)return t;const s=createSessionObject({gatewayKey:e,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels});return this.registerSession(s),this.logger.debug?.(`[bridge-core] ensureSessionForKey: lazily created session sid=${s.sessionId} key=${e}`),this.onSessionLoaded?.(s.sessionId,s.gatewaySessionKey,s.platformAgentId),s}async preloadSessionsFromGateway(){const e=this.sessionPreloadGeneration;if(this.sessionPreloadInFlight&&this.sessionPreloadInFlightGeneration===e)return await this.sessionPreloadInFlight;const t=this.preloadGatewaySessionIndex(e);this.sessionPreloadInFlight=t,this.sessionPreloadInFlightGeneration=e;try{await t,e===this.sessionPreloadGeneration&&this.sessionPreloadBarrierActive&&(this.sessionPreloadBarrierActive=!1,this.resolveSessionPreloadReady?.(),this.resolveSessionPreloadReady=null,this.rejectSessionPreloadReady=null)}catch(t){throw e===this.sessionPreloadGeneration&&this.sessionPreloadBarrierActive&&(this.sessionPreloadBarrierActive=!1,this.sessionPreloadFailed=!0,this.rejectSessionPreloadReady?.(t instanceof Error?t:new Error("session preload failed")),this.resolveSessionPreloadReady=null,this.rejectSessionPreloadReady=null),t}finally{this.sessionPreloadInFlight===t&&(this.sessionPreloadInFlight=null,this.sessionPreloadInFlightGeneration=null)}}async preloadGatewaySessionIndex(e){const t=this.getGateway();if(this.logSessionAuthority("session_preload_start",{generation:e,reason:t?.isConnected?"gateway_connected":"gateway_not_connected"}),!t?.isConnected)throw this.logSessionAuthority("session_preload_failure",{generation:e,reason:"gateway_not_connected"}),new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected for session hydration");try{const s=await t.sessionsList(),i=this.mergeGatewaySessions(s);this.logSessionAuthority("session_preload_success",{generation:e,loaded:i,reason:"authoritative_sessions_list"})}catch(t){throw this.logSessionAuthority("session_preload_failure",{generation:e,reason:t instanceof RuntimeCommandError?t.code:"sessions_list_failed"}),t}}mergeGatewaySessions(e,t){if(!e||"object"!=typeof e)return 0;const s=e.sessions;if(!Array.isArray(s))return 0;let i=0;for(const e of s){if(!e||"object"!=typeof e||Array.isArray(e))continue;const s=e,n=s.sessionKey??s.key??s.id??s.name;if("string"!=typeof n||!n.trim()||n.trim()!==n||t&&!t(n)||this.state.findSessionByKey(n))continue;const o=s.label||s.derivedTitle||s.title||void 0,r=o&&!isPlaceholderTitle(o)?o:void 0,a=createSessionObject({gatewayKey:n,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,title:r});this.registerSession(a,{ensureVerbose:!1}),i+=1}return i}async reconcileMissingGatewaySession(e,t){await this.assertSessionReconcileFence(t);const s=this.getGateway();if(!s?.isConnected)throw this.logSessionAuthority("session_reconcile_decision",{reason:"gateway_not_connected",runtimeSessionKeyHash:this.sessionKeyHash(e)}),new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected for session reconciliation");const i=await s.sessionsList();await this.assertSessionReconcileFence(t);const n=!e.startsWith("agent:"),o=[...openClawSessionKeysFromList(i)].filter(t=>t===e?!n:openClawKeyMatchesConversation(t,e));if(this.logSessionAuthority("session_reconcile_decision",{reason:o.length?"exact_candidates":"no_exact_candidate",runtimeSessionKeyHash:this.sessionKeyHash(e),candidateHashes:o.map(e=>this.sessionKeyHash(e))}),!o.length)return;const r=new Set(o),a=this.mergeGatewaySessions(i,e=>r.has(e));this.logSessionAuthority("session_reconcile_decision",{reason:"reconciled_exact_candidates",runtimeSessionKeyHash:this.sessionKeyHash(e),loaded:a,candidateHashes:o.map(e=>this.sessionKeyHash(e))})}async materializeMissingModelSessionKey(e,t){await this.assertSessionReconcileFence(t);const s=t.attempt,i=s.verifiedMessageGrant?.claims,n=i?.command;if("service"!==s.origin||!i||"conversation"!==i.scope||"session/set_model"!==i.method||"session.setModel"!==n?.name||"conversation"!==n.context.scope||i.runtimeSessionKey!==e||i.runtimeConversationUid!==e||n.context.conversation.runtimeSessionKey!==e||n.context.conversation.runtimeConversationUid!==i.runtimeConversationUid)throw this.logSessionAuthority("model_intent_materialization_rejected",{reason:"verified_service_target_required",runtimeSessionKeyHash:this.sessionKeyHash(e)}),new RuntimeCommandError("INVALID_PARAMS","model-intent session materialization requires an exact verified Service target");if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e))throw new RuntimeCommandError("INVALID_PARAMS","model-intent session materialization requires a canonical conversation UUID");let o;try{if(!this.runtimeAgentIdentities.resolveActiveLocalKey)throw new Error("identity resolver unavailable");o=await this.runtimeAgentIdentities.resolveActiveLocalKey(i.runtimeAgentUid)}catch{throw new RuntimeCommandError("INVALID_PARAMS","verified model-intent runtime agent is not active")}if(this.resolveLocalAgentId(i.platformAgentId)!==o)throw new RuntimeCommandError("INVALID_PARAMS","verified model-intent agent does not match the active platform projection");const r=`agent:${o}:beeos-conversation:ctx-${e}`,a=this.getGateway();if(!a?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected for model-intent session materialization");const l=await a.sessionsList();await this.assertSessionReconcileFence(t);const d=[...openClawSessionKeysFromList(l)].filter(t=>openClawKeyMatchesConversation(t,e));if(d.length){if(1===d.length&&d[0]===r)return this.mergeGatewaySessions(l,e=>e===r),r;throw new RuntimeCommandError("INVALID_PARAMS","verified model-intent target conflicts with an existing OpenClaw session")}return this.logSessionAuthority("model_intent_materialization_allowed",{runtimeSessionKeyHash:this.sessionKeyHash(e),openClawSessionKeyHash:this.sessionKeyHash(r)}),r}registerMaterializedModelSession(e){this.state.findSessionByKey(e)||(this.registerSession(createSessionObject({gatewayKey:e,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels}),{ensureVerbose:!1}),this.logSessionAuthority("model_intent_materialization_committed",{openClawSessionKeyHash:this.sessionKeyHash(e)}))}async assertSessionReconcileFence(e){if(e.lease.operationId!==e.attempt.operationId||e.lease.runtimeEpoch!==e.attempt.runtimeEpoch||e.attempt.abortSignal?.aborted)throw this.logSessionAuthority("session_reconcile_decision",{reason:"operation_fence_mismatch"}),new RuntimeCommandError("INVALID_PARAMS","session reconciliation is fenced");try{await this.runtimeLeases.renew(e.lease)}catch{throw this.logSessionAuthority("session_reconcile_decision",{reason:"lease_epoch_mismatch"}),new RuntimeCommandError("INVALID_PARAMS","session reconciliation is fenced")}if(e.attempt.abortSignal?.aborted)throw this.logSessionAuthority("session_reconcile_decision",{reason:"operation_aborted"}),new RuntimeCommandError("INVALID_PARAMS","session reconciliation is fenced")}sessionKeyHash(e){return createHash("sha256").update(e).digest("hex").slice(0,16)}logSessionAuthority(e,t){this.logger.info?.(`[bridge-core] ${JSON.stringify({event:e,...t})}`)}async handleRequest(e){this.writeObs({component:"bridge-core",domain:"forward",name:`rpc.request.${e.method}`,severity:"debug",payload:{id:e.id,method:e.method}});const t=e;try{switch(t.method){case"initialize":await this.handleInitialize(t);break;case"ping":this.transport.sendResult(t.id,{pong:!0});break;case"session/new":await this.handleSessionNew(t);break;case"session/load":await this.handleSessionLoad(t);break;case"session/list":await this.handleSessionList(t);break;case"session/rename":await this.handleSessionRename(t);break;case"session/delete":await this.handleSessionDelete(t);break;case"session/clear":await this.handleSessionClear(t);break;case"session/prompt":await this.handleSessionPrompt(t);break;case"session/set_mode":await this.handleSessionSetMode(t);break;case"session/set_model":await this.handleSessionSetModel(t);break;case"agent/create":await this.handleAgentCreate(t);break;case"agent/update":await this.handleAgentUpdate(t);break;case"agent/delete":await this.handleAgentDelete(t);break;case"agent/applyTemplate":await this.handleApplyTemplate(t);break;case"skills/list":await this.handleSkillsList(t);break;case"skills/install":await this.handleSkillsInstall(t);break;case"skills/uninstall":await this.handleSkillsUninstall(t);break;case"skills/update":await this.handleSkillsUpdate(t);break;case"models/list":await this.handleModelsList(t);break;case"session/cancel":await this.handleSessionCancel(t.id,t.params);break;case"cron/list":case"cron/status":case"cron/add":case"cron/update":case"cron/remove":case"cron/run":case"cron/runs":await this.handleCronRequest(t);break;default:"agent/list"===e.method?await this.handleAgentList(e):"_beeos/session/history"===e.method?await this.handleSessionHistoryPage(e):"mcp/list"===e.method?await this.handleMcpList(e):"mcp/set"===e.method?await this.handleMcpSet(e):"mcp/unset"===e.method?await this.handleMcpUnset(e):e.method.startsWith("terminal/")&&this.handleTerminalRequest?await this.handleTerminalRequest(e):this.transport.sendAcpError(e.id,Errors.MethodNotFound,{message:`Method not found: ${e.method}`})}}catch(t){this.transport.sendAcpError(e.id,Errors.InternalError,{message:t instanceof Error?t.message:"Internal error"})}}handleCanvasNotification=null;getCanvasState=null;onSessionLoaded=null;onSkillInstalled=null;onSessionDeleted=null;onSessionCleared=null;handleNotification(e){switch(e.method){case"session/cancel":this.observeLifecycle(this.handleSessionCancel(void 0,e.params),"session_cancel_notification");break;case"canvas/toggle":case"canvas/clear":case"canvas/reference":case"canvas/dimensions":this.observeLifecycle(this.dispatchLegacyCanvasNotification(e.method,e.params),`canvas_notification_${e.method.replace("/","_")}`);break;default:e.method.startsWith("terminal/")&&this.handleTerminalNotification?this.handleTerminalNotification(e.method,e.params):this.logger.debug?.(`[bridge-core] Unknown notification: ${e.method}`)}}async handleInitialize(e){await this.cronReady,this.initialized=!0;const t=this.transport.forwardControl;!1===e.params?.forwardThinking&&(t.forwardThinking=!1),!1===e.params?.forwardToolCalls&&(t.forwardToolCalls=!1),this.transport.sendResult(e.id,{protocolVersion:ACP_PROTOCOL_VERSION,agentCapabilities:{loadSession:!0,skills:!0,multipleSessions:!0,sessionCapabilities:{"web-ssh":!!this.config.bridge.shell.enabled,canvas:!!this.config.canvasEnabled},promptCapabilities:{image:!0,audio:!1,embeddedContext:!0},mcp:{}},agentInfo:{name:"beeos-claw",title:"BeeOS Agent Bridge",version:"0.1.0"},_meta:{},authMethods:[]})}async createRuntimeSession(e,t){const s=e.operationId.trim(),i=e.platformAgentId.trim();if(!s)throw new Error("session/new operationId is required");if(assertTrustedCoreExecutionAttempt(t,{operationId:s,runtimeEpoch:t.runtimeEpoch}),"service"!==t.origin)throw new Error("session/new runtime allocation requires Service authority");const n=this.resolveLocalAgentId(i);if(!i||!n)throw new RuntimeSessionNewRejectedError("session/new target agent is unknown");const o=`agent:${n}:service:${s}`,r=this.allocateSession({agentId:n,gatewaySessionKey:o,cwd:e.cwd??".",...void 0===e.title?{}:{title:e.title}});return await this.prepareSessionToolCatalog(n,o),{sessionId:r.sessionId,gatewaySessionKey:o,agentId:n,platformAgentId:i}}async handleSessionNew(e){const t="string"==typeof e.params?.agentId&&e.params.agentId||this.config.gateway.agentId||this.getDefaultAgentId(),s=`agent:${t}:${randomUUID()}`,i="string"==typeof e.params?.cwd?e.params.cwd:".",n=this.allocateSession({agentId:t,gatewaySessionKey:s,cwd:i});await this.prepareSessionToolCatalog(t,s),this.transport.sendResult(e.id,{sessionId:n.sessionId,modes:{availableModes:[{...DEFAULT_MODE}],currentModeId:DEFAULT_MODE.id},_meta:{sessionKey:s,agentId:n.agentId,platformAgentId:n.platformAgentId??null}}),this.fetchHistoryInBackground(s)}allocateSession(e){const t=this.state.findSessionByKey(e.gatewaySessionKey);if(t)return t;const s=createSessionObject({gatewayKey:e.gatewaySessionKey,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,...void 0===e.cwd?{}:{cwd:e.cwd},...void 0===e.title?{}:{title:e.title}});if(s.agentId!==e.agentId)throw new Error("session/new gateway key agent mismatch");return this.registerSession(s,{ensureVerbose:!1}),s}prepareWorkspaceCatalog(){this.scheduleCatalogPrepare({agentId:this.getDefaultAgentId(),mode:"catalog",keyKind:"boot"})}scheduleSessionToolCatalog(e,t){const s=classifyCatalogSessionKey(t);return this.runCatalogPrepare({agentId:e,sessionKey:t,mode:"im_conversation"===s?"effective":"both",keyKind:s})}scheduleCatalogPrepare(e){this.runCatalogPrepare(e)}async prepareSessionToolCatalog(e,t){await this.runCatalogPrepare({agentId:e,sessionKey:t,mode:"both",keyKind:classifyCatalogSessionKey(t)})}async runCatalogPrepare(e){const t=this.getGateway();if(!t?.isConnected||"function"!=typeof t.request)return;const s=`${e.mode}:${e.agentId}:${e.sessionKey??""}`;if(!this.catalogPrepareInFlight.has(s)){this.catalogPrepareInFlight.add(s);try{await prepareOpenClawToolCatalog({request:(e,s)=>t.request(e,s),agentId:e.agentId,...void 0===e.sessionKey?{}:{sessionKey:e.sessionKey},mode:e.mode,keyKind:e.keyKind,logger:this.logger})}finally{this.catalogPrepareInFlight.delete(s)}}}fetchHistoryInBackground(e){const t=this.getGateway();t?.isConnected&&loadHistory(t,e,50).catch(e=>{this.logger.debug?.(`[bridge-core] Background history fetch failed: ${e instanceof Error?e.message:e}`)})}async subscribeSessionMessages(e){if(e.messageSubscriptionActive)return;const t=this.getGateway();if(t?.isConnected)try{await t.sessionsMessagesSubscribe(e.gatewaySessionKey),e.messageSubscriptionActive=!0,this.logger.debug?.(`[bridge-core] session.message subscribed sessionKey=${e.gatewaySessionKey}`)}catch(t){this.logger.debug?.(`[bridge-core] session.message subscribe failed key=${e.gatewaySessionKey}: ${t instanceof Error?t.message:t}`)}}async resubscribeAllSessionMessages(){for(const e of this.state.sessions.values())e.messageSubscriptionActive=!1,await this.subscribeSessionMessages(e)}handleSessionMessage(e){const t=e?.sessionKey,s=e?.messageId;if(!t||!s)return;const i=this.state.sessionsByGatewayKey.get(t);if(!i)return;const n=e.message?.role;if("toolResult"===n||"tool"===n)return void this.handlePersistedToolResultMessage(i,e).catch(e=>{this.logger.warn?.(`[bridge-core] persisted toolResult handling failed sessionKey=${t} messageId=${s}: ${e instanceof Error?e.message:String(e)}`)});const o="user"===n?"user":"system"===n?"system":"assistant",r=i.lastTurnIdByRole?.[o];if(!r)return void this.logger.debug?.(`[bridge-core] session.message skipped: no lastTurnId role=${o} sessionKey=${t} messageId=${s}`);const a=e.message?.timestamp,l="number"==typeof a&&a>0?new Date(a).toISOString():void 0,d={turnId:r};"number"==typeof e.messageSeq&&(d.seq=e.messageSeq),this.transport.sendSessionUpdate(i.sessionId,{sessionUpdate:"message_id_bind",role:o,messageId:s,...l?{timestamp:l}:{}},d),i.lastTurnIdByRole&&(i.lastTurnIdByRole[o]=void 0),"assistant"===o&&reconcileMissingArtifacts({localHistory:this.localSessionHistory,runOutputRouter:this.runOutputRouter,config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:()=>this.getCanvasState?.()??null,isSessionAlive:e=>this.state.sessions.has(e)},{session:i,turnId:r,messageId:s,runId:r})}async handlePersistedToolResultMessage(e,t){const s=t.messageId;if(!s)return;const i=extractPersistedToolResultEnvelope(t.message),n=this.extractPersistedToolResultFromHistory(e,s),o=mergePersistedToolResultEnvelopes(n,i);if(!o?.toolCallId)return void this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.skipped_no_tool_call_id",severity:"warn",payload:{sessionId:e.sessionId,messageId:s}});n&&this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.hydrated_from_history",severity:"debug",payload:{sessionId:e.sessionId,messageId:s,toolCallId:o.toolCallId,toolName:o.toolName}});const r=e.toolCallTurns?.get(o.toolCallId),a=r?.turnId??resolveTurnId(e);if(!a)return void this.writeObs({component:"bridge-core",domain:"tool_result",name:"tool_result.skipped_no_turn",severity:"warn",payload:{sessionId:e.sessionId,messageId:t.messageId,toolCallId:o.toolCallId,toolName:o.toolName}});const l=projectPersistedToolResultForReplay({toolCallId:o.toolCallId,toolName:o.toolName||r?.toolName||"tool",...o.rawText?{rawText:o.rawText}:{},...o.rawResult?{rawResult:o.rawResult}:{},...o.timestamp?{timestamp:o.timestamp}:{}});await emitToolResultProjectionForTurn({session:e,projection:l,toolCallId:o.toolCallId,turnId:a,config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:()=>this.getCanvasState?.()??null,sendUpdate:s=>(a&&void 0===s._turnId&&(s._turnId=a),this.transport.sendSessionUpdate(e.sessionId,s,{turnId:a,..."number"==typeof t.messageSeq?{seq:t.messageSeq}:{}}))})}extractPersistedToolResultFromHistory(e,t){if(!e.agentId)return;const s=this.localSessionHistory.readEntries({agentId:e.agentId,sessionKey:e.gatewaySessionKey,sessionId:e.sessionId});return findPersistedToolResultEnvelopeByMessageId(s,t)}async recoverSessionFromGateway(e){const t=this.getGateway();if(t?.isConnected)try{const s=await t.sessionsList();if(!s||"object"!=typeof s)return;const i=s.sessions;if(!Array.isArray(i))return;for(const t of i){const s=t,i=s.sessionKey??s.key??s.id??s.name;if(i&&"string"==typeof i&&deriveSessionId(i,this.instanceId)===e){const e=s.label||s.derivedTitle||s.title||void 0,t=e&&!isPlaceholderTitle(e)?e:void 0,n=createSessionObject({gatewayKey:i,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,title:t});return this.registerSession(n),n}}}catch(e){this.logger.debug?.("[bridge-core] recoverSessionFromGateway failed:",e)}}async resolveOrCreateSession(e){let t=this.state.sessions.get(e);if(!t&&e.startsWith("agent:")&&(t=this.state.findSessionByKey(e)),t||e.startsWith("agent:")||(t=await this.recoverSessionFromGateway(e)),t)return{session:t,created:!1};const s=e.startsWith("agent:"),i=this.config.gateway.agentId||this.getDefaultAgentId();return t=createSessionObject({gatewayKey:s?e:`agent:${i}:${e}`,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels}),this.registerSession(t),{session:t,created:!0}}async handleSessionLoad(e){const t=e.params?.sessionId;if(!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const{session:s}=await this.resolveOrCreateSession(t),i=s.sessionId;this.state.lastActiveSessionId=i,s.chatReplayDedupKeys.clear(),s.sessionLoadInProgress=!0;try{const e=await this.emitHistoryTurns(s);this.emitCurrentStreamSnapshot(s,e)}finally{s.sessionLoadInProgress=!1}this.transport.sendResult(e.id,{sessionId:i}),this.onSessionLoaded?.(i,s.gatewaySessionKey,s.platformAgentId),this.pushSkillsList().catch(e=>{this.logger.warn?.(`[bridge-core] pushSkillsList failed: ${e instanceof Error?e.message:e}`)})}async emitHistoryTurns(e){const t=new Set,s=e.sessionId;try{const i=this.getGateway(),n=e.assistantMq&&!e.assistantMq.closed,o=n?null:i,r=await loadHistory(o,e.gatewaySessionKey,100,{localHistory:this.localSessionHistory,agentId:e.agentId,sessionId:e.sessionId,logger:this.logger,emitObs:e=>{this.writeObs({component:"history-replay",domain:"lifecycle",name:e.name,severity:e.severity,payload:e.payload})}}),a=groupIntoTurns(r),l=a.filter(e=>"user"===e.role).length,d=a.filter(e=>"assistant"===e.role).length,c=a.filter(e=>e.toolCalls?.length).length,m=a.reduce((e,t)=>e+(t.toolCalls?.length??0),0);if(this.logger.debug?.(`[bridge-core] handleSessionLoad sid=${s} messages=${r.length} turns=${a.length} userTurns=${l} asstTurns=${d} turnsWithToolCalls=${c} totalToolCalls=${m}`),l>0&&e.pendingUserTurnUpdates&&e.pendingUserTurnUpdates.length>0&&(e.pendingUserTurnUpdates=[]),a.length>0&&isPlaceholderTitle(e.title)&&"manual"!==e.titleSource){const t=a.find(e=>{if("user"!==e.role)return!1;if("internal_system"===e.provenance?.kind)return!1;if(isCronPromptText(e.content))return!1;const t=cleanHistoryUserContent(e.content);return t.length>0&&!/^Sender\s*\(untrusted/i.test(t)});if(t?.content){const i=cleanHistoryUserContent(t.content).slice(0,64).replace(/\n/g," ").trim();if(i){e.title=i,e.titleSource="truncated";const t=this.getGateway();t?.isConnected&&t.sessionsPatch(e.gatewaySessionKey,{label:i}).catch(()=>{}),this.transport.sendSessionUpdate(s,{sessionUpdate:"title_update",title:i}),this.tryGenerateTitleFromHistory(e,s,a)}}}const u=t=>`history:${e.gatewaySessionKey}:${t}`;let p=-1;if(n)for(let e=a.length-1;e>=0;e--)if("assistant"===a[e].role){p=e;break}const{replayedToolCallIds:h,streamCompleteCount:g}=await this.replayHistoryTurnsToSink(e,a,this.transport,{mqActive:Boolean(n),lastAsstIdx:p,turnIdFor:(e,t)=>u(t)});for(const e of h)t.add(e);this.logger.debug?.(`[bridge-core] emitHistoryTurns DONE sid=${s} historyStreamCompleteEmitted=${g} replayedToolCallIds=${t.size}`)}catch(e){this.logger.warn?.("[bridge-core] Failed to load session history:",e)}return t}async replayHistoryTurnsToSink(e,t,s,i){const n=e.sessionId,o=new Set;let r=0;for(let a=0;a<t.length;a++){const l=t[a],d=i.turnIdFor(l,a);if(i.mqActive&&a===i.lastAsstIdx){const e=classifyHistoricalAssistantTurnCompletion(l,{mqActive:i.mqActive,isSkippedForLiveMq:!0});this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] assistant — ${"live"===e.kind?e.reason:"mq active"}, deferring to emitCurrentStreamSnapshot`);continue}if("user"===l.role&&"internal_system"===l.provenance?.kind){this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] internal_system user message`);continue}if("user"===l.role&&/^\s*system\s*\(untrusted[^)]*\)/i.test(l.content)){this.logger.debug?.(`[bridge-core] SKIP history_turn[${a}] system-injected user message`);continue}if("user"===l.role&&isHeartbeatPromptText(l.content)){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] heartbeat cron prompt sessionId=${e.sessionId} contentLen=${l.content.length} preview="${previewSanitizedText(l.content)}"`);continue}if("user"===l.role&&isCronPromptText(l.content)){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] cron-triggered user message sessionId=${e.sessionId} contentLen=${l.content.length} preview="${previewSanitizedText(l.content)}"`);continue}if("assistant"===l.role){const t=classifyHeartbeatFilter(l.content);if(t.filtered){this.logger.info?.(`[bridge-core] SKIP history_turn[${a}] heartbeat assistant reply reason=${t.reason} sessionId=${e.sessionId} contentLen=${l.content.length} preview="${previewSanitizedText(l.content)}"`);continue}}const c="assistant"===l.role?classifyHistoricalAssistantTurnCompletion(l,{mqActive:i.mqActive,isSkippedForLiveMq:!1}):void 0;if("error"===c?.kind||"interrupted"===c?.kind&&c.failure)s.sendSessionUpdate(n,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:""},...l.timestamp?{timestamp:l.timestamp}:{}},{turnId:d,...void 0!==l._seq?{seq:l._seq}:{}}),s.sendSessionUpdate(n,{sessionUpdate:"stream_complete",stopReason:c.stopReason,error:c.message,errorCode:c.errorCode},{turnId:d}),this.logger.info?.(`[bridge-core] history_runtime_failure_terminalized session_id=${n} turn_id=${d} error_code=${c.errorCode} stop_reason=${c.stopReason} source=history_replay`),r+=1;else if("system"!==l.role)if(this.logger.debug?.(`[bridge-core] SENDING history_turn[${a}] role=${l.role} contentLen=${l.content.length} content=${JSON.stringify(l.content.slice(0,80))} tcCount=${l.toolCalls?.length??0}`),"user"===l.role)this.replayUserTurnAsChunks(s,n,l.content,l.metadata,l.timestamp,d,l._seq);else{if(await this.replayAssistantTurnAsStream(s,n,l,d),l.toolCalls)for(const e of l.toolCalls)e.id&&o.add(e.id);const e=c??classifyHistoricalAssistantTurnCompletion(l,{mqActive:i.mqActive,isSkippedForLiveMq:!1});if("interrupted"===e.kind){const t=new Set(e.incompleteToolCallIds);for(const i of l.toolCalls??[])t.has(i.id)&&s.sendSessionUpdate(n,buildToolCallUpdatePayload({toolCallId:i.id,toolName:i.name,outputText:e.message,kind:i.kind,status:"failed",timestamp:l.timestamp}),{turnId:d});this.logger.info?.(`[bridge-core] history_orphan_turn_terminalized session_id=${n} turn_id=${d} tool_call_ids=${e.incompleteToolCallIds.join(",")} stop_reason=${e.stopReason} source=history_replay`),s.sendSessionUpdate(n,{sessionUpdate:"stream_complete",stopReason:e.stopReason,error:e.message,errorCode:e.errorCode},{turnId:d}),r+=1;continue}if("completed"!==e.kind)continue;s.sendSessionUpdate(n,{sessionUpdate:"stream_complete",stopReason:e.stopReason},{turnId:d}),r+=1}else this.logger.debug?.(`[bridge-core] SENDING system_message[${a}] contentLen=${l.content.length}`),s.sendSessionUpdate(n,{sessionUpdate:"system_message_chunk",content:{type:"text",text:l.content},metadata:l.metadata,...l.timestamp?{timestamp:l.timestamp}:{}},{turnId:d,...void 0!==l._seq?{seq:l._seq}:{}})}return{replayedToolCallIds:o,streamCompleteCount:r}}async handleSessionHistoryPage(e){const t=e.params??{},s="string"==typeof t.sessionId?t.sessionId:"";if(!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const i="string"==typeof t.cursor&&t.cursor.length>0?t.cursor:void 0,n="number"==typeof t.limit&&Number.isFinite(t.limit)&&t.limit>0?Math.min(1e3,Math.floor(t.limit)):100,{session:o}=await this.resolveOrCreateSession(s),r=this.getGateway(),a=await loadHistoryPage(r,o.gatewaySessionKey,{...i?{cursor:i}:{},limit:n,logger:this.logger}),l=groupIntoTurns(a.messages),d=new HistoryUpdateRecorder(this.transport);await this.replayHistoryTurnsToSink(o,l,d,{mqActive:!1,lastAsstIdx:-1,turnIdFor:(e,t)=>void 0!==e._seq?`history:${o.gatewaySessionKey}:seq:${e._seq}`:`history:${o.gatewaySessionKey}:seq:none:${i??"head"}:${t}`}),this.logger.debug?.(`[bridge-core] _beeos/session/history sid=${o.sessionId} cursor=${i??"<head>"} limit=${n} turns=${l.length} updates=${d.updates.length} hasMore=${a.hasMore} nextCursor=${a.nextCursor??"<none>"}`),this.transport.sendResult(e.id,{updates:d.updates,...a.nextCursor?{nextCursor:a.nextCursor}:{},hasMore:a.hasMore})}emitCurrentStreamSnapshot(e,t){const s=e.pendingUserTurnUpdates;if(s&&s.length>0)for(const t of s)this.transport.sendSessionUpdate(e.sessionId,t);const i=e.assistantMq;if(i&&!i.closed){const s=i.peek(0);let n=0;if(t&&t.size>0)for(let e=0;e<s.length;e++){const i=s[e],o=i.sessionUpdate,r=i.toolCallId;if("tool_call"!==o&&"tool_call_update"!==o||!r||!t.has(r)){if("agent_message_chunk"===o){if(""===(i.content?.text??"").trim()&&e<s.length-1){const i=s[e+1];if(("tool_call"===i.sessionUpdate||"tool_call_update"===i.sessionUpdate)&&i.toolCallId&&t.has(i.toolCallId)){n=e+1;continue}}break}break}n=e+1}const o=resolveTurnId(e),r=o?{turnId:o}:void 0;for(let t=n;t<s.length;t++)this.transport.sendSessionUpdate(e.sessionId,s[t],r)}}async handleSessionList(e){let t;try{t=parseRuntimeSessionListPage(e.params)}catch(t){return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:t instanceof Error?t.message:"Invalid session/list params"})}const s=[];for(const[,e]of this.state.sessions)isBackgroundSessionKey(e.gatewaySessionKey)||this.cronIsolatedSessionKeys.has(e.gatewaySessionKey)||!t.includeDeleted&&this.state.deletedGatewaySessionKeys.has(e.gatewaySessionKey)||s.push({sessionId:e.sessionId,title:e.title||"New chat",cwd:e.cwd,updatedAt:e.lastStreamActivityTs??e.createdAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(e.gatewaySessionKey)});const i=this.getGateway();if(i?.isConnected)try{const e=await i.sessionsList();if(e&&"object"==typeof e&&Array.isArray(e.sessions)){const i=e.sessions;for(const e of i){const i=e,n=i.sessionKey??i.key??i.id??i.name;if(!n||"string"!=typeof n)continue;if(isBackgroundSessionKey(n))continue;if(this.cronIsolatedSessionKeys.has(n))continue;const o=deriveSessionId(n,this.instanceId),r=s.find(e=>e.sessionId===n||e.sessionId===o||this.state.sessions.get(e.sessionId)?.gatewaySessionKey===n);if(!0===i.deleted){if(this.state.deletedGatewaySessionKeys.add(n),!t.includeDeleted)continue}else this.state.deletedGatewaySessionKeys.delete(n);const a=i.label||cleanGatewayDerivedTitle(i.derivedTitle)||i.title||void 0,l=a&&!isPlaceholderTitle(a)?a:void 0,d=i.model,c=i.modelProvider;if(r){if(l&&isPlaceholderTitle(r.title)){r.title=l;const e=this.state.sessions.get(r.sessionId);e&&isPlaceholderTitle(e.title)&&(e.title=l)}const e=i.updatedAt;e&&(r.updatedAt=e),d&&(r.model=d),c&&(r.modelProvider=c)}else{const e=createSessionObject({gatewayKey:n,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,cwd:i.cwd,title:l});this.registerSession(e,{ensureVerbose:!1}),s.push({sessionId:e.sessionId,title:l||"",cwd:i.cwd,updatedAt:i.updatedAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(n),model:d,modelProvider:c})}}}}catch(e){this.logger.debug?.("[bridge-core] sessions.list enrichment failed:",e)}const n=t.includeDeleted?s:s.filter(e=>{const t=this.state.sessions.get(e.sessionId)?.gatewaySessionKey;return!t||!this.state.deletedGatewaySessionKeys.has(t)});n.sort((e,t)=>(t.updatedAt??0)-(e.updatedAt??0)||e.sessionId.localeCompare(t.sessionId)),this.transport.sendResult(e.id,{sessions:pageRuntimeSessions(n,t)})}async handleSessionRename(e){const t=e.params?.sessionId,s=e.params?.title;if(!t||!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, title"});const i=s.slice(0,64),n=this.state.sessions.get(t);if(n)try{const s=await this.dispatchLegacyConversationCapability("session.rename",n,{title:i});n.title=s.title,n.titleSource="manual",this.transport.sendResult(e.id,{sessionId:t,title:s.title})}catch(s){this.logger.warn?.(`[bridge-core] ACP rename projection deferred: ${s instanceof Error?s.message:String(s)}`),n.title=i,n.titleSource="manual",this.transport.sendResult(e.id,{sessionId:t,title:i})}else this.transport.sendAcpError(e.id,Errors.SessionNotFound)}static TITLE_TEMPLATES=[{match:["今日日报","日报"],zh:"今日工作日报",en:"Daily Work Log"},{match:["周报","周报总结","本周周报"],zh:"本周工作周报",en:"Weekly Work Report"},{match:["月报","月度汇报","月度总结"],zh:"本月工作月报",en:"Monthly Work Report"},{match:["工作日志","日志"],zh:"工作日志整理",en:"Work Log Review"},{match:["晨会","今日晨会"],zh:"今日晨会纪要",en:"Daily Standup Notes"},{match:["复盘","今日复盘"],zh:"今日工作复盘",en:"Daily Retrospective"}];static TITLE_SYSTEM_PROMPT='You are a conversation title generator. Your task is to generate a concise, human-readable title summarizing the topic of the user\'s question together with the assistant\'s answer.\n\n### Rules:\n- Length: 3-6 words in English, or 6-20 characters in Chinese/Japanese/Korean (up to 24 if needed to keep all key domain terms intact).\n- Language: Match the user\'s input language exactly. Mixed zh/en input → use the dominant language. Do NOT switch languages. (IM path injects Client UI locale via conversation-title.ts — keep ACP in sync with that module when changing examples.)\n- Extract the core topic (noun-centric) of the Q&A exchange, not the action verb.\n- Use the assistant\'s answer to disambiguate or enrich the topic when the question alone is vague (e.g. "帮我看下" + an answer about x402 → "x402 支付集成").\n- No punctuation, no quotation marks, no emojis, no markdown.\n- No prefix like "Title:" or "Here is". Output the title only.\n- Avoid generic words: "help", "question", "chat", "problem", "task".\n- Greetings and self-introductions ARE concrete topics (e.g. "自我介绍", "自己紹介", "Self Introduction") — never treat them as vague.\n- NEVER return placeholder / empty-chat titles such as "新会话", "新對話", "New chat", "New Conversation", "新しいチャット", "새 채팅", "แชตใหม่". Always invent a concrete noun-centric topic from whatever words are present.\n\n### Input Format:\nThe user message will contain both the question and the assistant\'s answer, delimited like this:\n[User Question]\n...question text...\n\n[Assistant Answer]\n...answer text (may be truncated with "..." in the middle)...\n\n### Output Format:\nRespond ONLY with valid JSON, no other text:\n{"title": "<your title here>"}\n\n### Examples:\nInput:\n[User Question]\n帮我写一个关于BeeOS设备接入的PRD\n\n[Assistant Answer]\n好的,这是 BeeOS 设备接入 PRD 的草稿:1. 目标 ...\nOutput: {"title": "BeeOS 设备接入 PRD"}\n\nInput:\n[User Question]\n帮我看下\n\n[Assistant Answer]\n你说的 x402 支付协议是一个基于 HTTP 402 的链上支付标准 ...\nOutput: {"title": "x402 支付协议解析"}\n\nInput:\n[User Question]\nWhat\'s the best way to integrate x402 payment protocol?\n\n[Assistant Answer]\nYou have three options: 1. Use the official SDK ...\nOutput: {"title": "x402 Payment Integration"}\n\nInput:\n[User Question]\n用一句话介绍一下你自己。\n\n[Assistant Answer]\n我是 BeeOS 智能助手,可以帮助你完成设备控制与任务协作。\nOutput: {"title": "自我介绍"}\n\nInput:\n[User Question]\nhi\n\n[Assistant Answer]\nHello! How can I help you today?\nOutput: {"title": "Greeting"}';matchTitleTemplate(e){const t=e.trim().toLowerCase();for(const s of AcpGatewayBridgeCore.TITLE_TEMPLATES)if(s.match.some(e=>e.toLowerCase()===t))return/[\u4e00-\u9fff]/.test(e)?s.zh:s.en}isGuardMessage(e){const t=e.trim();return[...t].length<3||[...t].every(e=>/[\p{P}\p{S}\s]/u.test(e))}truncateQuestionForTitle(e){return e.length>2e3?e.slice(0,1500):e}truncateAnswerForTitle(e){return e.length<=1e3?e:`${e.slice(0,500)}\n...\n${e.slice(-500)}`}generateTitleFromQA(e,t,s,i){if(this.isGuardMessage(s))return void this.logger.info?.(`[bridge-core] title-gen SKIP guard message sid=${t}`);const n=this.matchTitleTemplate(s);if(n){e.title=n,e.titleSource="template";const s=this.getGateway();return s?.isConnected&&s.sessionsPatch(e.gatewaySessionKey,{label:n}).catch(()=>{}),void this.transport.sendSessionUpdate(t,{sessionUpdate:"title_update",title:n})}const o=(process.env.AROUTER_BASE_URL||"").replace(/\/$/,""),r=process.env.AROUTER_API_KEY||"";if(!o||!r)return void this.logger.info?.(`[bridge-core] title-gen SKIP sid=${t}: no AROUTER env vars`);const a=`[User Question]\n${this.truncateQuestionForTitle(s)}\n\n[Assistant Answer]\n${this.truncateAnswerForTitle(i)}`;this.logger.info?.(`[bridge-core] title-gen CALLING LLM sid=${t} q="${s.slice(0,60)}" aLen=${i.length}`),fetch(`${o}/v1/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`},body:JSON.stringify({model:"openai/gpt-4o-mini",max_tokens:32,temperature:.3,messages:[{role:"system",content:AcpGatewayBridgeCore.TITLE_SYSTEM_PROMPT},{role:"user",content:a}]})}).then(async s=>{if(!s.ok){const e=await s.text().catch(()=>"");return void this.logger.info?.(`[bridge-core] title-gen FAILED sid=${t} status=${s.status} body=${e.slice(0,300)}`)}const i=await s.json(),n=i.choices?.[0]?.message?.content?.trim();if(!n)return;const o=n.replace(/^```json\s*/,"").replace(/```$/,"").trim();let r;try{r=JSON.parse(o).title?.trim()??""}catch{return void this.logger.debug?.(`[bridge-core] title-gen JSON parse failed: ${o}`)}if(!r)return;if(isPlaceholderTitle(r))return void this.logger.debug?.("[bridge-core] title-gen discarded: placeholder LLM title");if("manual"===e.titleSource)return void this.logger.debug?.("[bridge-core] title-gen discarded: user already renamed");this.logger.info?.(`[bridge-core] title-gen OK sid=${t} title="${r}"`),e.title=r,e.titleSource="auto";const a=this.getGateway();a?.isConnected&&a.sessionsPatch(e.gatewaySessionKey,{label:r}).catch(()=>{}),this.transport.sendSessionUpdate(t,{sessionUpdate:"title_update",title:r})}).catch(e=>{this.logger.info?.(`[bridge-core] title-gen ERROR sid=${t}: ${e instanceof Error?e.message:e}`)})}async tryGenerateTitleFromHistory(e,t,s){if("manual"===e.titleSource||"auto"===e.titleSource)return;const i=this.getGateway();if(!i?.isConnected)return;let n=s;if(!n)try{const t=await loadHistory(i,e.gatewaySessionKey);n=groupIntoTurns(t)}catch(e){return void this.logger.debug?.(`[bridge-core] title-gen loadHistory failed sid=${t}: ${e instanceof Error?e.message:e}`)}const o=n.find(e=>{if("user"!==e.role)return!1;if(!e.content?.trim())return!1;if("internal_system"===e.provenance?.kind)return!1;if(isCronPromptText(e.content))return!1;const t=cleanHistoryUserContent(e.content);return!!t&&!/^Sender\s*\(untrusted/i.test(t)});if(!o)return;const r=n.indexOf(o),a=n.slice(r+1).find(e=>"assistant"===e.role&&!!e.content?.trim());if(!a)return;const l=cleanHistoryUserContent(o.content),d=a.content.trim();l&&d&&this.generateTitleFromQA(e,t,l,d)}async handleSessionDelete(e){const t=e.params?.sessionId;if(!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const s=this.state.sessions.get(t);if(!s)return this.logger.info?.(`[bridge-core] session/delete sessionId=${t} NOT FOUND in local state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);const i=s.gatewaySessionKey,n=this.sessionMutationCoordinator.tryStartExclusive({sessionKey:i,label:"session_delete"},"acp");if(!n.ok)return void this.sendSessionBusy(s,e.id,"active_session_mutation",n.queueDepth);const o=n.token;try{if(this.hasActiveRun(s)){const t=this.sessionMutationCoordinator.gateSnapshot(i);return void this.sendSessionBusy(s,e.id,"active_session_mutation",t.queueDepth)}this.logger.info?.(`[bridge-core] session/delete sessionId=${t} gatewayKey=${i} gwConnected=${!!this.getGateway()?.isConnected}`);let n=!1;const o=this.getGateway();if(o?.isConnected&&i)try{await o.sessionsDelete(i),n=!0,this.logger.info?.(`[bridge-core] Gateway sessions.delete SUCCESS for key=${i}`)}catch(t){const s=t instanceof Error?t.message:String(t);if(this.logger.info?.(`[bridge-core] Gateway sessions.delete FAILED for key=${i}: ${s}`),!s.includes("Cannot delete the main session"))return void this.transport.sendResult(e.id,{deleted:!1});try{await o.sessionsReset(i),n=!0,this.logger.info?.(`[bridge-core] Gateway sessions.reset SUCCESS for key=${i}`)}catch(t){return this.logger.warn?.(`[bridge-core] Gateway sessions.reset also failed for key=${i}: ${t instanceof Error?t.message:t}`),void this.transport.sendResult(e.id,{deleted:!1})}}else i&&this.logger.warn?.(`[bridge-core] Gateway not connected, proceeding with local-only delete for key=${i}`);this.state.clearStreamIdleTimer(s),s.assistantMq?.close(),s.assistantMq=void 0,s.currentRunId&&this.state.promptsByRunId.delete(s.currentRunId);const r=new Set;for(const[e,s]of this.state.toolCallToSessionMap)s===t&&(this.state.toolCallToSessionMap.delete(e),r.add(e));for(const[e,t]of this.state.toolCallIdMap)r.has(t)&&this.state.toolCallIdMap.delete(e);this.state.sessions.delete(t),this.state.sessionsByGatewayKey.delete(i),this.state.deletedGatewaySessionKeys.add(i),this.transport.resetSessionUpdateCount(t),n&&this.onSessionDeleted?.(i),this.logger.info?.(`[bridge-core] session/delete DONE sessionId=${t} gatewayKey=${i} deleted=true`),this.transport.sendResult(e.id,{deleted:!0})}finally{this.sessionMutationCoordinator.releaseBeforeRuntime(o)}}async handleSessionClear(e){this.logger.info?.(`[bridge-core] handleSessionClear called, params=${JSON.stringify(e.params)}`);const t=e.params?.sessionId;if(!t)return this.logger.warn?.("[bridge-core] session/clear missing sessionId"),void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const s=this.state.sessions.get(t);if(!s)return this.logger.warn?.(`[bridge-core] session/clear sessionId=${t} not found in state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);this.logger.info?.(`[bridge-core] session/clear sessionId=${t} gatewayKey=${s.gatewaySessionKey}`);const i=this.getGateway(),n=i?.isConnected;if(!s.promptDone&&(void 0!==s.pendingPromptId||void 0!==s.currentRunId)){this.logger.info?.(`[bridge-core] session/clear: cancelling active run sid=${t} runId=${s.currentRunId}`);const o=s.writeGateToken;let r=!1;if(n&&o){const e=await this.sessionMutationCoordinator.requestCancel(o,i);r="recovery_required"===await this.handleCancellationResult(s,e)}else n&&await i.chatAbort(s.gatewaySessionKey,s.currentRunId);return void this.transport.sendError(e.id,Errors.AgentError.rpcCode,r?SESSION_RECOVERY_REQUIRED_MESSAGE:"Cancellation is still converging",r?{code:SESSION_RECOVERY_REQUIRED_CODE,stopReason:"interrupted"}:{code:"session_cancellation_pending",stopReason:"cancelled"})}try{await this.dispatchLegacyConversationCapability("session.clear",s,{}),this.transport.sendResult(e.id,{cleared:!0}),this.logger.info?.("[bridge-core] session/clear DONE, sent cleared=true")}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleSessionSetMode(e){const t=e.params?.sessionId,s=e.params?.mode,i=t?this.state.sessions.get(t):void 0;if(t&&s)if(i)try{const t=await this.dispatchLegacyConversationCapability("session.setMode",i,{mode:s});this.transport.sendResult(e.id,t)}catch(t){this.sendRuntimeCommandError(e.id,t)}else this.transport.sendAcpError(e.id,Errors.SessionNotFound);else this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, mode"})}async handleSessionSetModel(e){const t=e.params,s=null===t||"object"!=typeof t||Array.isArray(t)?void 0:t,i="string"==typeof s?.sessionId?s.sessionId:void 0;if(!i||!s||!Object.prototype.hasOwnProperty.call(s,"model"))return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, model"});const n=s.model;if(null!==n&&("string"!=typeof n||0===n.length||n.trim()!==n))return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"model must be a non-empty string or null"});const o=i?this.state.sessions.get(i):void 0;if(o)try{const t=await this.dispatchLegacyConversationCapability("session.setModel",o,{modelOverrideId:n});this.transport.sendResult(e.id,{model:t.effectiveModelId,modelProvider:t.modelProvider??null})}catch(t){this.sendRuntimeCommandError(e.id,t)}else this.transport.sendAcpError(e.id,Errors.SessionNotFound)}sendRuntimeCommandError(e,t){if(t instanceof RuntimeCommandError)switch(t.code){case"MISSING_PARAM":return void this.transport.sendAcpError(e,Errors.MissingParam,{...t.param?{param:t.param}:{}});case"INVALID_PARAMS":return void this.transport.sendAcpError(e,Errors.InvalidParams,{message:t.message});case"GATEWAY_NOT_CONNECTED":return void this.transport.sendAcpError(e,Errors.GatewayNotConnected);case"EXECUTION_FAILED":case"AGENT_DEFAULT_MODEL_NOT_CONFIGURED":case"OUTCOME_UNKNOWN":case"INSTANCE_EPOCH_MISMATCH":case"COMMAND_EXPIRED":case"IDEMPOTENCY_CONFLICT":case"UNAUTHORIZED_TARGET":return void this.transport.sendAcpError(e,Errors.InternalError,{message:t.message})}else this.transport.sendAcpError(e,Errors.InternalError,{message:t instanceof Error?t.message:"Internal error"})}createOperationId(){const e=randomUUID();return`${e.slice(0,14)}7${e.slice(15)}`}canonicalContextBase(e,t=this.createOperationId(),s="0"){const i=Date.now(),n=createHash("sha256").update(JSON.stringify(e)).digest("hex");return{operationId:t,runtimeInstanceUid:this.instanceId,dispatchEpoch:s,paramsHash:n,idempotencyKeyHash:createHash("sha256").update(t).digest("hex"),claimDeadline:new Date(i+3e4).toISOString(),executionDeadline:new Date(i+3e5).toISOString()}}acpExecutionAttempt(e){return createAcpCoreExecutionAttempt({operationId:e.context.operationId,runtimeEpoch:e.context.dispatchEpoch,resources:this.runtimeCommandResources,historyBoundary:this.localHistoryBoundary})}instanceCommand(e,t,s){return{name:e,params:t,context:{...this.canonicalContextBase(t,s.operationId,s.runtimeEpoch),scope:"instance"}}}async agentCommand(e,t,s,i){return{name:e,params:s,context:{...this.canonicalContextBase(s,i.operationId,i.runtimeEpoch),scope:"agent",agent:{runtimeLocalKey:t,runtimeAgentUid:await this.runtimeAgentIdentities.resolveOrCreate(t)}}}}async dispatchLegacyAgentCapability(e,t,s){const i=normalizeRuntimeAgentLocalKey(t),n=await this.runtimeLeases.claim(this.createOperationId());let o=!1;try{const t={name:e,params:s,context:{...this.canonicalContextBase(s,n.operationId,n.runtimeEpoch),scope:"agent",agent:{runtimeLocalKey:i,runtimeAgentUid:await this.runtimeAgentIdentities.resolveOrCreate(i)}}},r=this.runtimeCommands.dispatch(t,this.acpExecutionAttempt(t));return o=!0,await r.completion}finally{o||await this.runtimeLeases.release(n)}}async dispatchLegacyInstanceCapability(e,t){const s=await this.runtimeLeases.claim(this.createOperationId());let i=!1;try{const n={name:e,params:t,context:{...this.canonicalContextBase(t,s.operationId,s.runtimeEpoch),scope:"instance"}},o=this.runtimeCommands.dispatch(n,this.acpExecutionAttempt(n));return i=!0,await o.completion}finally{i||await this.runtimeLeases.release(s)}}async handleMcpList(e){const t=await this.dispatchLegacyInstanceCapability("mcp.list",{});this.transport.sendResult(e.id,{servers:Object.fromEntries(t.servers.map(e=>[e.name,{transport:e.transport,...e.url?{url:e.url}:{},...e.command?{command:e.command}:{},...e.args?{args:[...e.args]}:{},...e.cwd?{cwd:e.cwd}:{},...e.connectionTimeoutMs?{connectionTimeoutMs:e.connectionTimeoutMs}:{}}]))})}async handleMcpSet(e){const t=e.params&&"object"==typeof e.params?e.params:{},s="string"==typeof t.name?t.name:"";let i;try{if(requireSecretFreeMcpServerName(s),!t.server||"object"!=typeof t.server||Array.isArray(t.server))throw new Error("mcp/set requires name and server");const e=t.server,n=extractMcpCredentialMaterial(e),o={...e};delete o.headers,delete o.env;const r=e.transport??("string"==typeof e.command?"stdio":"string"==typeof e.url?"streamable-http":void 0),a=normalizeMcpPublicServerConfig({...o,transport:r}),l=createHash("sha256").update(canonicalizeJcs(a)).digest("hex");i={definition:{definitionRef:`acp-mcp:sha256:${l}`,version:"local-v1",sha256:l,...n?{credentialRef:await requireCredentialReferencePort(this.mcpCredentialReferences).put({serverName:s,material:n})}:{}},config:structuredClone(a)}}catch(t){return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:t instanceof Error?t.message:"Invalid MCP server config"})}this.runtimeCommandResources.registerMcpDefinition?.(i.definition,i.config);try{await this.dispatchLegacyInstanceCapability("mcp.set",{name:s,definition:i.definition})}catch(e){if(i.definition.credentialRef){!await this.mcp.removeCredentialIfUnreferenced(i.definition.credentialRef)&&this.mcpCredentialReferences?.owns(i.definition.credentialRef)&&await this.mcpCredentialReferences.remove(i.definition.credentialRef)}throw e}this.transport.sendResult(e.id,{ok:!0,restarting:!1})}async handleMcpUnset(e){const t=e.params&&"object"==typeof e.params?e.params:{};let s;try{s=requireSecretFreeMcpServerName(t.name)}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"mcp/unset requires name"})}await this.dispatchLegacyInstanceCapability("mcp.unset",{name:s}),this.transport.sendResult(e.id,{ok:!0,restarting:!1})}async dispatchLegacyConversationCapability(e,t,s){const i=await this.runtimeLeases.claim(this.createOperationId());let n=!1;try{const o={name:e,params:s,context:{...this.canonicalContextBase(s,i.operationId,i.runtimeEpoch),scope:"conversation",conversation:{runtimeConversationUid:t.sessionId,runtimeSessionKey:t.gatewaySessionKey}}},r=this.runtimeCommands.dispatch(o,this.acpExecutionAttempt(o));return n=!0,await r.completion}finally{n||await this.runtimeLeases.release(i)}}async dispatchLegacyTerminalCapability(e,t,s,i){const n=await this.runtimeLeases.claim(this.createOperationId());let o=!1;try{const r={name:e,params:i,context:{...this.canonicalContextBase(i,n.operationId,n.runtimeEpoch),scope:"terminal",terminal:{terminalId:t,clientId:s}}},a=this.runtimeCommands.dispatch(r,this.acpExecutionAttempt(r));return o=!0,await a.completion}finally{o||await this.runtimeLeases.release(n)}}async dispatchLegacyCanvasNotification(e,t){const s=t&&"object"==typeof t?t:{},i=e.replace("/","."),n="canvas.toggle"===i?{visible:!0===s.visible||!0===s.enabled}:"canvas.reference"===i?{surfaceId:"string"==typeof s.surfaceId?s.surfaceId:""}:"canvas.dimensions"===i?{width:Number(s.width),height:Number(s.height),..."number"==typeof s.devicePixelRatio?{devicePixelRatio:s.devicePixelRatio}:{}}:{},o="string"==typeof s.sessionId?s.sessionId:"acp",r="string"==typeof s.canvasId?s.canvasId:o,a=await this.runtimeLeases.claim(this.createOperationId());let l=!1;try{const e={name:i,params:n,context:{...this.canonicalContextBase(n,a.operationId,a.runtimeEpoch),scope:"canvas",canvas:{canvasId:r,sessionId:o}}},t=this.runtimeCommands.dispatch(e,this.acpExecutionAttempt(e));l=!0,await t.completion}finally{l||await this.runtimeLeases.release(a)}}async applyCanonicalCanvasControl(e,t){if("canvas/reference"===e){const e=this.state.resolveSessionFromParams(t);if(!e)throw new RuntimeCommandError("INVALID_PARAMS","canvas session not found");const s="string"==typeof t.surfaceId?t.surfaceId:void 0;return void(e.referencedSurfaceId=s||void 0)}if(!this.handleCanvasNotification)throw new RuntimeCommandError("INVALID_PARAMS","canvas control delegate is not configured");await this.handleCanvasNotification(e,t)}async dispatchLegacyCronCapability(e,t){const s=normalizeRuntimeAgentLocalKey(this.config.gateway.agentId),i=await this.runtimeLeases.claim(this.createOperationId());let n=!1;try{const o="cron.add"===e?{...t,runtimeCronJobUid:`runtime-cron:${i.operationId}`}:"cron.run"===e?{...t,runToken:`runtime-run:${i.operationId}`}:t,r={name:e,params:o,context:{...this.canonicalContextBase(o,i.operationId,i.runtimeEpoch),scope:"agent",agent:{runtimeLocalKey:s,runtimeAgentUid:await this.runtimeAgentIdentities.resolveOrCreate(s)}}},a=this.runtimeCommands.dispatch(r,this.acpExecutionAttempt(r));return n=!0,await a.completion}finally{n||await this.runtimeLeases.release(i)}}validateConnectedGateway(){if(!this.getGateway()?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected")}async finishClaimedLegacyAgentMutation(e,t){let s=!1;try{const e=await t(),i=this.runtimeCommands.dispatch(e,this.acpExecutionAttempt(e));s=!0;const n=await i.completion;return queueMicrotask(()=>{this.observeAgentProjection()}),n}finally{s||await this.runtimeLeases.release(e)}}async observeAgentProjection(){await(async(e,t)=>{if(!t)return;let s;try{await Promise.race([Promise.resolve().then(t),new Promise((t,i)=>{s=setTimeout(()=>i(new Error(`${e} timed out`)),this.projectionObservationTimeoutMs)})])}catch(t){this.logger.warn?.(`[acp] ${e} failed: ${t instanceof Error?t.message:String(t)}`)}finally{s&&clearTimeout(s)}})("agent projection sync",this.triggerAgentSync)}async handleAgentCreate(e){try{const t=e.params?.name?.trim()??"";if(!t)throw new RuntimeCommandError("INVALID_PARAMS","Missing agent name");this.validateConnectedGateway();const s=this.createOperationId(),i=await this.runtimeLeases.claim(s),n=await this.finishClaimedLegacyAgentMutation(i,async()=>{const s=await this.runtimeAgentIdentities.allocateCreate(i,"agent.create"),n=Object.entries(e.params?.files??{}).filter(e=>"string"==typeof e[1]).map(([e,t])=>({name:e,contentRef:this.runtimeCommandResources.registerContent(t)}));return this.instanceCommand("agent.create",{runtimeAgentUid:s.runtimeAgentUid,name:t,...e.params?.identity?{identity:e.params.identity}:{},...e.params?.model?{defaultModelId:e.params.model}:{},...n.length?{bootstrapFiles:n}:{}},i)});this.transport.sendResult(e.id,{agentId:n.runtimeLocalKey,platformAgentId:this.resolvePlatformAgentId(n.runtimeLocalKey)??"",workspace:n.workspacePath})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleApplyTemplate(e){try{const t=e.params?.name?.trim()??"";if(!t)throw new RuntimeCommandError("INVALID_PARAMS","Missing agent name");const s=e.params?.packageUrl?.trim()??"";if(!s)throw new RuntimeCommandError("MISSING_PARAM","Missing packageUrl","packageUrl");this.validateConnectedGateway();const i=await this.runtimeCommandResources.registerArtifact(s,e.params?.sha256),n=this.createOperationId(),o=await this.runtimeLeases.claim(n),r=await this.finishClaimedLegacyAgentMutation(o,async()=>{const s=await this.runtimeAgentIdentities.allocateCreate(o,"agent.applyTemplate");return this.instanceCommand("agent.applyTemplate",{runtimeAgentUid:s.runtimeAgentUid,name:t,artifact:i,...e.params?.identity?{identity:e.params.identity}:{},...e.params?.model?{defaultModelId:e.params.model}:{}},o)});this.transport.sendResult(e.id,{agentId:r.runtimeLocalKey,platformAgentId:this.resolvePlatformAgentId(r.runtimeLocalKey)??"",workspace:r.workspacePath})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleAgentUpdate(e){try{const t=e.params?.agentId?.trim()??"";if(!t)throw new RuntimeCommandError("MISSING_PARAM","Missing agentId","agentId");const s={...void 0!==e.params?.name?{name:e.params.name}:{},...void 0!==e.params?.model?{defaultModelId:e.params.model}:{},...void 0!==e.params?.identity?{identity:e.params.identity}:{}};if(0===Object.keys(s).length)throw new RuntimeCommandError("INVALID_PARAMS","No updatable fields provided (name/model/identity)");this.validateConnectedGateway();const i=await this.runtimeLeases.claim(this.createOperationId()),n=await this.finishClaimedLegacyAgentMutation(i,()=>this.agentCommand("agent.update",t,s,i));this.transport.sendResult(e.id,{agentId:n.runtimeLocalKey})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleAgentDelete(e){try{const t=e.params?.agentId?.trim()??"";if(!t)throw new RuntimeCommandError("MISSING_PARAM","Missing agentId","agentId");if("main"===normalizeRuntimeAgentLocalKey(t))throw new RuntimeCommandError("INVALID_PARAMS",'"main" cannot be deleted; delete the instance instead');this.validateConnectedGateway();const s=await this.runtimeLeases.claim(this.createOperationId()),i=await this.finishClaimedLegacyAgentMutation(s,()=>this.agentCommand("agent.delete",t,void 0===e.params?.deleteFiles?{}:{deleteFiles:e.params.deleteFiles},s));this.transport.sendResult(e.id,{agentId:i.runtimeLocalKey,...void 0===i.removedBindings?{}:{removedBindings:i.removedBindings}})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleAgentList(e){try{const t=await this.dispatchLegacyInstanceCapability("agent.listProjection",{}),s=t.agents.map(e=>({agentId:e.runtimeLocalKey,name:e.name,...e.emoji?{emoji:e.emoji}:{},...e.avatarUrl?{avatarUrl:e.avatarUrl}:{}})),i={...t.defaultRuntimeLocalKey?{defaultId:t.defaultRuntimeLocalKey}:{},agents:s};this.transport.sendResult(e.id,i)}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleModelsList(e){try{this.transport.sendResult(e.id,await this.dispatchLegacyInstanceCapability("models.list",{}))}catch(t){this.sendRuntimeCommandError(e.id,t)}}async handleSkillsList(e){try{const t=await this.dispatchLegacyAgentCapability("skills.list",e.params?.agentId,{});this.transport.sendResult(e.id,{skills:t.skills.map(e=>({name:e.name,description:e.description??"",...e.emoji?{emoji:e.emoji}:{},...e.always?{always:!0}:{},...e.bundled?{bundled:!0}:{},...e.enabled?{}:{disabled:!0},skillKey:e.skillKey,...e.iconUrl?{iconUrl:e.iconUrl}:{}}))})}catch(t){this.sendRuntimeCommandError(e.id,t)}}async fetchSimplifiedSkills(e){const t=this.getGateway();if(!t?.isConnected)return[];try{const s=await t.skillsStatus(e);if(!s?.skills)return[];const i=s.skills.filter(e=>!1!==e.eligible||!0===e.disabled).sort((e,t)=>e.always&&!t.always?-1:!e.always&&t.always?1:e.name.localeCompare(t.name)).map(e=>({name:e.name,description:e.description,emoji:e.emoji,always:e.always||void 0,bundled:e.bundled||void 0,disabled:e.disabled||void 0,skillKey:e.skillKey||void 0,iconUrl:void 0})),n=await this.getSkillsDir(e);for(const e of i){const t=e.skillKey??e.name;try{const s=await readFile(join(n,t,".beeos-meta.json"),"utf8"),i=JSON.parse(s);i?.iconUrl&&(e.iconUrl=i.iconUrl)}catch{}}return i}catch(e){return this.logger.warn?.(`[bridge-core] Failed to fetch skills: ${e instanceof Error?e.message:e}`),[]}}async pushSkillsList(e){const t=await this.fetchSimplifiedSkills(e);this.transport.sendNotification("session/update",{sessionId:"_system",update:{sessionUpdate:"available_commands_update",commands:t}}),this.writeObs({component:"bridge-core",domain:"lifecycle",name:"skills.pushed",severity:"debug",payload:{count:t.length}})}getOpenclawHome(){return process.env.OPENCLAW_HOME||join(homedir(),".openclaw")}async getSkillsDir(e){const t=this.getOpenclawHome();if(!e||"main"===e)return join(t,"skills");const s=await this.resolveAgentWorkspaceDir(e);return join(s??join(t,`workspace-${e}`),"skills")}async resolveAgentWorkspaceDir(e){const t=this.getGateway();if(!t?.isConnected)return null;try{const s=await t.request("agents.list",{}),i=normalizeAgentId(e),n=s?.agents?.find(e=>normalizeAgentId(e.id)===i);return n?.workspace??null}catch(t){return this.logger.warn?.(`[bridge-core] resolveAgentWorkspaceDir("${e}") failed: ${t instanceof Error?t.message:t}`),null}}async handleSkillsInstall(e){const t=e.params,s=t?.zipUrl,i=t?.files,n=t?.agentId;let o;try{o=validateSkillSlug(t?.slug||t?.name||"")}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Invalid or missing skill slug"})}if(i?.length||s)try{let r;if(s){r=parseSkillZip(await fetchWithRetry(s,{maxRetries:4,logger:this.logger}))}else{if(!i?.length)throw new RuntimeCommandError("INVALID_PARAMS","Missing skill files or zipUrl");r=new Map;for(const e of i){const t=normalizeSkillArtifactPath(e.path),s=await fetchWithRetry(e.downloadUrl,{maxRetries:4,logger:this.logger});r.set(t,new Uint8Array(s))}}if(t?.iconUrl&&r.set(".beeos-meta.json",(new TextEncoder).encode(JSON.stringify({iconUrl:t.iconUrl}))),!this.runtimeCommandResources.registerParsedArtifact)throw new RuntimeCommandError("EXECUTION_FAILED","ACP skill artifact ingress is not configured");const a=this.runtimeCommandResources.registerParsedArtifact({manifest:{slug:o,version:"0.0.0"},workspaceFiles:r});await this.dispatchLegacyAgentCapability("skills.install",n,{artifact:a}),this.transport.sendResult(e.id,{success:!0}),this.onSkillInstalled?.(o,resolveSkillInstallDir(await this.getSkillsDir(n),o)),await this.pushSkillsList(n)}catch(t){this.logger.error?.(`[bridge-core] Failed to install skill "${o}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Install failed: ${t instanceof Error?t.message:String(t)}`})}else this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skill files or zipUrl"})}async handleSkillsUninstall(e){const t=e.params,s=t?.agentId;let i;try{i=validateSkillSlug(t?.slug||t?.name||"")}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Invalid or missing skill name"})}try{await this.dispatchLegacyAgentCapability("skills.uninstall",s,{skillKey:i}),this.transport.sendResult(e.id,{success:!0}),await this.pushSkillsList(s)}catch(t){this.logger.error?.(`[bridge-core] Failed to uninstall skill "${i}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Uninstall failed: ${t instanceof Error?t.message:String(t)}`})}}async handleSkillsUpdate(e){const t=e.params,s=t?.skillKey,i=t?.enabled,n=t?.agentId;if(s&&"boolean"==typeof i)try{await this.dispatchLegacyAgentCapability("skills.setEnabled",n,{skillKey:s,enabled:i}),this.logger.info?.(`[bridge-core] Updated skill "${s}" enabled=${i}${n?` for agent "${n}"`:""}`),this.transport.sendResult(e.id,{ok:!0,skillKey:s,enabled:i}),await this.pushSkillsList(n)}catch(t){this.logger.error?.(`[bridge-core] Failed to update skill "${s}": ${t instanceof Error?t.message:t}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Update failed: ${t instanceof Error?t.message:String(t)}`})}else this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skillKey or enabled"})}async handleSessionPrompt(e){const t=e.params?.sessionId,s=e.params?.prompt;if(!t||!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, prompt"});const{session:i,created:n}=await this.resolveOrCreateSession(t);n&&this.onSessionLoaded?.(i.sessionId,i.gatewaySessionKey,i.platformAgentId);const o=i.sessionId;this.state.lastActiveSessionId=o,this.onPromptStart?.();const r=this.getGateway();if(!r?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);if(this.hasActiveRun(i)){const t=this.sessionMutationCoordinator.gateSnapshot(i.gatewaySessionKey);return void this.sendSessionBusy(i,e.id,"active_run",t.queueDepth)}try{await this.ensureVerboseFull(i)}catch(t){return this.releaseSessionWriteGate(i),void this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Failed to prepare agent context: ${t instanceof Error?t.message:String(t)}`})}let a=extractPromptBlocks(sanitizePromptPayload(s));const l=a.filter(e=>"text"===e.type&&"text"in e&&"string"==typeof e.text).map(e=>e.text).join("\n");if(i.fileArtifactDisplayMode=prefersChatFileArtifacts(l)?"chat":"auto",!i.title||isPlaceholderTitle(i.title)){const e=a.find(e=>"text"===e.type&&e.text);if(e&&"text"in e&&e.text){const t=stripTransportMetadata(e.text).trim();if(this.logger.info?.(`[bridge-core] title-gen sid=${o} existing="${i.title}" cleanText="${t.slice(0,80)}" isPlaceholder=${isPlaceholderTitle(t)}`),t&&!isPlaceholderTitle(t)){const e=t.slice(0,64).replace(/\n/g," ").trim();i.title=e,i.titleSource="truncated",r?.isConnected&&e&&r.sessionsPatch(i.gatewaySessionKey,{label:e}).catch(e=>{this.logger.debug?.(`[bridge-core] sessionsPatch (title) failed: ${e instanceof Error?e.message:e}`)}),this.transport.sendSessionUpdate(o,{sessionUpdate:"title_update",title:e})}}}const d=buildResolutionPlan(a);if(d.refs.length>0)try{const e=await resolveResources(d,this.writeObs);a=applyResolutions(a,e)}catch(e){this.logger.warn?.(`[bridge-core] Resource pre-resolution failed: ${e instanceof Error?e.message:e}`)}let c;if(this.beeosFileResolver){const e=this.beeosFileResolver.buildResolutionPlan(a);if(e.refs.length>0)try{c=await this.beeosFileResolver.resolveMetadataForPrompt(e)}catch(e){this.logger.warn?.(`[bridge-core] BeeOS file resolution failed: ${e instanceof Error?e.message:e}`)}}const m=toGatewayPrompt(a,{beeosFileResolutionsByUri:c});if("error"in m)return this.releaseSessionWriteGate(i),void this.transport.sendAcpError(e.id,Errors.PromptConvertFailed,{message:m.error});const{message:u,attachments:p}=m,h=resolveStandaloneSlashCommandName(a,u)??resolveSlashCmd(a,u),g="string"==typeof h;if(h&&this.handleSlashCommand){const t=this;if(await(async()=>{try{return await t.handleSlashCommand(h)}catch(e){return t.logger.warn?.(`[bridge-core] /${h} handler failed: ${e instanceof Error?e.message:e}`),!1}})())return this.emitPromptAsUserSessionUpdates(i,a,resolveTurnId(i)),this.transport.sendSessionUpdate(o,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:`Loading ${h}...`},timestamp:(new Date).toISOString()},{turnId:resolveTurnId(i)}),this.transport.sendResult(e.id,{stopReason:"end_turn"}),void this.releaseSessionWriteGate(i)}const y="chat.send";let f=g?u:withUserMessagePrefix(u);if(this.config.canvasEnabled&&!g&&!0===i.canvasActive&&!i.channelType){const e=this.getCanvasState?.();if(e){const t=e.resolveCanvasForSession(i.gatewaySessionKey);f=`${buildActiveSurfacesContext(e,t)??"[Canvas] Canvas panel is available. Use render_ui for structured/visual content."}\n\n${f}`}const t=i.referencedSurfaceId;t&&(f=`${CANVAS_UPDATE_HINT}\n[IMPORTANT: The user selected canvas surface "${t}" for editing. You MUST call render_ui with id="${t}" to UPDATE this existing surface. Do NOT create a new surface with a different id.]\n${f}`,i.referencedSurfaceId=void 0)}if(!g){const t=parseMentionsParam(e.params?.mentions);if(t.length>0){i.activeMentions=t;f=`${buildMentionInstruction(t)}\n${f}`,this.logger.debug?.(`[bridge-core] delegation mention instruction injected for ${t.length} agent(s)`)}else i.activeMentions=void 0}if(g&&this.logger.info?.(`[bridge-core] slash command passthrough sessionId=${o} command=/${h}`),this.hasActiveRun(i)){const t=this.sessionMutationCoordinator.gateSnapshot(i.gatewaySessionKey);return void this.sendSessionBusy(i,e.id,"active_run_after_prepare",t.queueDepth)}const w=this.sessionMutationCoordinator.tryStartInteractive({sessionKey:i.gatewaySessionKey,label:"session/prompt"},"acp");if(!w.ok)return void this.sendSessionBusy(i,e.id,w.reason,w.queueDepth);i.writeGateToken=w.token,this.state.resetForNewRun(i,o,this.transport),i.pendingPromptId=e.id,i.gatewayMethod=y;const I=resolveTurnId(i);this.emitPromptAsUserSessionUpdates(i,a,I),this.transport.sendSessionUpdate(o,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:""},timestamp:(new Date).toISOString()},I?{turnId:I}:void 0);const v=extractCronJobId(u);v&&(i.cronJobId=v);const S=p.length>0?p.map(e=>({type:e.type,content:e.content,...e.mimeType?{mimeType:e.mimeType}:{},...e.fileName?{fileName:e.fileName}:{}})):void 0,C=`req_${randomUUID().replace(/-/g,"")}`,R={sessionKey:i.gatewaySessionKey,message:f,deliver:!1,idempotencyKey:randomUUID()};S?.length&&(R.attachments=S);if(!r.sendFrame({type:"req",id:C,method:y,params:R}))return i.pendingPromptId=void 0,i.assistantMq?.close(),i.assistantMq=void 0,i.pendingUserTurnUpdates=void 0,this.releaseSessionWriteGate(i),void this.transport.sendAcpError(e.id,Errors.SendFailed,{message:"failed to send prompt to gateway"});i.gatewayRequestId=C,this.state.promptsByGatewayRequestId.set(C,i)}replayUserTurnAsChunks(e,t,s,i,n,o,r){const a=splitUserReplayBlocks(s),l=o||void 0!==r?{...o?{turnId:o}:{},...void 0!==r?{seq:r}:{}}:void 0;for(const s of a){let o;if("text"===s.type){if(!s.text)continue;o={type:"text",text:s.text}}else o="resource_link"===s.type?{type:"resource_link",uri:s.uri??"",...s.fileName?{name:s.fileName}:{},...s.mimeType?{mimeType:s.mimeType}:{}}:{type:s.type,...s.mimeType?{mimeType:s.mimeType}:{},...s.fileName?{fileName:s.fileName}:{}};e.sendSessionUpdate(t,{sessionUpdate:"user_message_chunk",content:o,...i?{metadata:i}:{},...n?{timestamp:n}:{}},l)}}async replayAssistantTurnAsStream(e,t,s,i){const n=e=>{if(i||void 0!==e)return{...i?{turnId:i}:{},...void 0!==e?{seq:e}:{}}},o=new Set,r=(s,i,r)=>{if(!s?.length)return;const a=n(r);for(const n of s)n.uri&&!o.has(n.uri)&&(o.add(n.uri),e.sendSessionUpdate(t,{sessionUpdate:"agent_message_chunk",content:{type:"resource_link",uri:n.uri,name:n.name??"file",...n.mimeType?{mimeType:n.mimeType}:{}},...i?{timestamp:i}:{}},a))},a=n(s._seq);if(!s.sourceMessages?.length){const i={sessionUpdate:"history_turn",role:s.role,content:s.content,reasoning:s.reasoning,toolCalls:s.toolCalls,...s.timestamp?{timestamp:s.timestamp}:{}};return s.metadata&&(i.metadata=s.metadata),e.sendSessionUpdate(t,i,a),void r(s.files,s.timestamp,s._seq)}for(const i of s.sourceMessages){const o=i.updateType??"agent_message_chunk",a=n(i._seq);switch(o){case"agent_message_chunk":e.sendSessionUpdate(t,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:i.content},...i.timestamp?{timestamp:i.timestamp}:{},...i.metadata?{metadata:i.metadata}:{}},a);break;case"agent_thought_chunk":e.sendSessionUpdate(t,{sessionUpdate:"agent_thought_chunk",content:{type:"text",text:i.content},...i.timestamp?{timestamp:i.timestamp}:{}},a);break;case"tool_call":{const n=i.toolCallId??"",o=s.toolCalls?.find(e=>e.id===n),r=i.toolName??"tool",l=i.content??"";let d={};if(l)try{d=JSON.parse(l)}catch{}e.sendSessionUpdate(t,buildToolCallPayload({toolCallId:n,toolName:r,args:d,kind:o?.kind,status:o?.status??"in_progress",timestamp:i.timestamp}),a);break}case"tool_call_update":{const n=i.toolCallId??"",o=s.toolCalls?.find(e=>e.id===n),l=i.toolName??o?.name??"tool",d=i.content??o?.output??"",c=await this.resolveReplayResourceLinks(n,l,i.files);e.sendSessionUpdate(t,buildToolCallUpdatePayload({toolCallId:n,toolName:l,outputText:d,kind:o?.kind,status:o?.status??"completed",resourceLinks:c,timestamp:i.timestamp}),a),r(c,i.timestamp,i._seq);break}}}r(s.files,s.timestamp,s._seq)}async resolveReplayResourceLinks(e,t,s){let i=s?.map(e=>({uri:e.uri,name:e.name??"file",...e.mimeType?{mimeType:e.mimeType}:{}}));if(i?.length)return i;if(i=await readEnrichmentCache(e).catch(()=>{}),i?.length)return i;if("beeos_upload_file"!==t)return;const n=await readUploadToolResult(e).catch(()=>{});if(!n)return;const o=buildToolResultSessionContentBlocks(t,n).filter(e=>"resource_link"===e.content?.type&&e.content.uri).map(e=>({uri:e.content.uri,name:e.content.name??"file",...e.content.mimeType?{mimeType:e.content.mimeType}:{}}));return o.length>0?o:void 0}emitPromptAsUserSessionUpdates(e,t,s){const i=(new Date).toISOString(),n=s?{turnId:s}:void 0;for(const o of t){const t=this.toUserSessionUpdateContent(o);if(!t)continue;const r={sessionUpdate:"user_message_chunk",content:t,timestamp:i};s&&void 0===r._turnId&&(r._turnId=s),this.transport.sendSessionUpdate(e.sessionId,r,n),e.pendingUserTurnUpdates?.push(r)}}toUserSessionUpdateContent(e){if("text"===e.type){if(!e.text)return;return{type:"text",text:e.text}}if("image"===e.type){const t=e.mimeType??e.mime_type??"application/octet-stream",s=e.fileName??e.file_name??e.name;if(!e.data&&!e.uri)return;return{type:"image",...e.data?{data:e.data}:{},...e.uri?{uri:e.uri}:{},...t?{mimeType:t}:{},...s?{fileName:s}:{}}}if("resource_link"===e.type){if(!e.uri)return;return{type:"resource_link",uri:e.uri,...e.title?{title:e.title}:{},...e.name?{name:e.name}:{},...e.mimeType??e.mime_type?{mimeType:e.mimeType??e.mime_type}:{}}}if("file"===e.type){const t=e.mimeType??e.mime_type??"application/octet-stream",s=e.fileName??e.file_name??e.filename??e.name;if(!e.data&&!e.text&&!e.uri)return;return{type:"file",...e.data?{data:e.data}:{},...e.text?{text:e.text}:{},...e.uri?{uri:e.uri}:{},...t?{mimeType:t}:{},...s?{fileName:s}:{}}}if("resource"===e.type){const t=e.resource??{},s=t.uri??e.uri,i=t.mimeType??t.mime_type??e.mimeType??e.mime_type,n=t.text??e.text,o=t.data??e.data,r=t.fileName??t.file_name??t.filename??t.name??e.fileName??e.file_name??e.filename??e.name;if(!s&&!n&&!o)return;return{type:"resource",resource:{...s?{uri:s}:{},...i?{mimeType:i}:{},...n?{text:n}:{},...o?{data:o}:{},...r?{fileName:r}:{}}}}}async handleSessionCancel(e,t){const s=t&&"object"==typeof t&&"sessionId"in t?t.sessionId:void 0;if(!s)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.MissingParam,{param:"sessionId"}));const i=this.state.sessions.get(s);if(!i)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.SessionNotFound));const n=i.activeRunBinding??this.observeActiveRun(i);if(!n){const t=this.sessionMutationCoordinator.snapshot(i.gatewaySessionKey);if("exclusive"===t?.attachmentPolicy&&void 0!==e){const t=this.sessionMutationCoordinator.gateSnapshot(i.gatewaySessionKey);return void this.sendSessionBusy(i,e,"exclusive_session_mutation",t.queueDepth)}return void(void 0!==e&&this.transport.sendResult(e,{}))}const o=await this.runtimeLeases.claim(this.createOperationId());let r=!1;try{const s=t&&"object"==typeof t&&"reason"in t&&"string"==typeof t.reason?t.reason:void 0,i={targetMessageId:n.targetMessageId,...s?{reason:s}:{}},a={name:"session.cancel",params:i,context:{...this.canonicalContextBase(i,o.operationId,o.runtimeEpoch),scope:"conversation",historyGeneration:n.historyGeneration,conversation:{runtimeConversationUid:n.runtimeConversationUid,runtimeSessionKey:n.runtimeSessionKey}}},l=this.runtimeCommands.dispatch(a,this.acpExecutionAttempt(a));r=!0,await l.completion,void 0!==e&&this.transport.sendResult(e,{})}catch(t){void 0!==e&&this.sendRuntimeCommandError(e,t)}finally{r||await this.runtimeLeases.release(o)}}hasActiveRun(e){return!e.promptDone&&(void 0!==e.pendingPromptId||void 0!==e.currentRunId||void 0!==e.gatewayRequestId)}observeLifecycle(e,t,s){observeLifecyclePromise(e,this.logger,{component:"bridge-core",action:t,...s?{sessionId:s.sessionId,gatewaySessionKey:s.gatewaySessionKey}:{}})}async handleCancellationResult(e,t){return t.ok?(this.observeLifecycle(t.terminalObservation.then(t=>{if(!t.ok)return this.publishCancellationRecovery(e)}),"cancel_terminal_observation",e),"accepted"):"recovery"in t?(await this.publishCancellationRecovery(e),"recovery_required"):"terminal_already_observed"}async publishCancellationRecovery(e){await this.terminateRunOutput(e,{kind:"failed",mode:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE})}releaseSessionWriteGate(e){if(!e.writeGateToken)return;const t=e.writeGateToken;e.writeGateToken=void 0,this.sessionMutationCoordinator.releaseBeforeRuntime(t)}logRuntimeFailure(e,t){this.logger.warn?.(`[bridge-core] runtime_failure_classified session_id=${e.sessionId} gateway_session_key=${e.gatewaySessionKey} run_id=${e.currentRunId??""} gateway_request_id=${e.gatewayRequestId??""} error_code=${t.errorCode} stop_reason=${t.stopReason} source=${t.source} runtime=${t.runtimeKind}`)}async commitRunOutputTerminal(e,t){const s=this.state.sessions.get(e.sessionId);if(!s||s.gatewaySessionKey!==e.gatewaySessionKey)throw new Error(`run output lifecycle session unavailable: ${e.sessionId}`);await this.commitRunOutputTerminalForSession(s,t)}async commitRunOutputTerminalForSession(e,t){if("complete"!==t.kind&&"cancelled"!==t.kind)return"interrupted"===t.mode?(t.classification&&this.logRuntimeFailure(e,t.classification),void await this.endRun(e,{kind:"interrupted",stopReason:t.classification?.stopReason??"interrupted",message:t.message,errorCode:t.errorCode})):void await this.endRun(e,{kind:"error",code:t.code,message:t.message});await this.endRun(e,{kind:"complete",stopReason:t.stopReason})}async terminateRunOutput(e,t){const s=resolveTurnId(e);s&&this.runOutputRouter.hasRoute(s)?await this.runOutputRouter.terminal(s,t):await this.commitRunOutputTerminalForSession(e,t)}sendSessionBusy(e,t,s,i){this.logger.warn?.(`[bridge-core] session_write_gate_busy session_id=${e.sessionId} gateway_session_key=${e.gatewaySessionKey} reason=${s} queue_depth=${i}`),this.transport.sendError(t,Errors.AgentError.rpcCode,"Agent is busy",{code:"session_busy",reason:s,queueDepth:i}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:"interrupted",error:INTERRUPTED_PUBLIC_MESSAGE,errorCode:"session_busy"},{turnId:`req:${String(t)}`})}async endRun(e,t){if(e.promptDone||e.mutationTerminalCommitting)return this.logger.debug?.(`[bridge-core] endRun skipped (promptDone=true) sessionId=${e.sessionId} outcome=${t.kind}`),!1;if(void 0===e.pendingPromptId&&void 0===e.currentRunId&&void 0===e.gatewayRequestId)return this.logger.debug?.(`[bridge-core] endRun skipped (no pendingPromptId and no currentRunId) sessionId=${e.sessionId} outcome=${t.kind}`),!1;e.mutationTerminalCommitting=!0,this.state.clearStreamIdleTimer(e);const s=e.writeGateToken,i=e.activeRunBinding;let n,o,r,a,l=t;if(s){const e="complete"===l.kind?"cancelled"===l.stopReason?"cancelled":"completed":"interrupted"===l.kind&&"timeout"===l.stopReason?"timeout":"error",t=await this.sessionMutationCoordinator.converge(s,e);t.ok?n=t.permit:l={kind:"interrupted",stopReason:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE}}try{const t=resolveTurnId(e),s=t?{turnId:t}:void 0;e.promptDone=!0;const i=e.currentRunId;if("complete"===l.kind){if(i&&this.onRunComplete){const t=e.agentAssistantTextSoFar??e.chatAssistantTextSoFar??"";this.onRunComplete(i,t,l.stopReason)}void 0!==e.pendingPromptId&&this.transport.sendResult(e.pendingPromptId,{stopReason:l.stopReason}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:l.stopReason},s),this.tryGenerateTitleFromHistory(e,e.sessionId)}else"interrupted"===l.kind?(i&&this.onRunFailed&&this.onRunFailed(i,l.message),void 0!==e.pendingPromptId&&this.transport.sendError(e.pendingPromptId,Errors.AgentError.rpcCode,l.message,{code:l.errorCode??"interrupted",stopReason:l.stopReason}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:l.stopReason,error:l.message,...l.errorCode?{errorCode:l.errorCode}:{}},s)):(i&&this.onRunFailed&&this.onRunFailed(i,l.message),void 0!==e.pendingPromptId&&this.transport.sendError(e.pendingPromptId,l.code,l.message),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:"error",error:l.message},s))}catch(e){o=e}try{this.cleanupPrompt(e)}catch(e){r=e}finally{e.writeGateToken=void 0,e.activeRunBinding=void 0,n&&this.sessionMutationCoordinator.release(n)}if(i){const e="complete"===l.kind?"cancelled"===l.stopReason?"cancelled":"completed":"interrupted"===l.kind&&"cancelled"===l.stopReason?"cancelled":"failed";this.activeRuns.markTerminal(i,e)}try{this.onRunEnded?.(e,l)}catch(e){a=e}if(o)throw o;if(r)throw r;if(a)throw a;return!0}adoptBackgroundMutation(e,t){const s=this.sessionMutationCoordinator.attachOrStartExternal({sessionKey:e.gatewaySessionKey,label:"background_run"},"acp",t);return s.ok?("owner"===s.ownership&&(e.writeGateToken=s.token),s):(this.logger.error?.(`[bridge-core] background mutation rejected sessionKey=${e.gatewaySessionKey} runId=${t} reason=${s.reason}`),s)}cleanupPrompt(e){this.state.clearStreamIdleTimer(e);const t=e.currentRunId;if(t){this.state.promptsByRunId.delete(t),this.state.runIdToCronJobId.delete(t),this.state.markRunCompleted(t);const e=`tc_${t}_`;for(const[t,s]of this.state.toolCallIdMap)s.startsWith(e)&&this.state.toolCallIdMap.delete(t)}e.gatewayRequestId&&this.state.promptsByGatewayRequestId.delete(e.gatewayRequestId),e.pendingPromptId=void 0,e.promptDone=void 0,e.currentRunId=void 0,e.gatewayRequestId=void 0,e.assistantStreamStartedAt=void 0,e.gatewayMethod=void 0,e.mutationTerminalCommitting=void 0,e.assistantMq?.close(),e.assistantMq=void 0,e.pendingUserTurnUpdates=void 0}async completePrompt(e,t){"cancelled"!==t?await this.terminateRunOutput(e,{kind:"complete",stopReason:t}):await this.terminateRunOutput(e,{kind:"cancelled",stopReason:"cancelled"})}async interruptPrompt(e,t){await this.terminateRunOutput(e,{kind:"failed",mode:"interrupted",message:t.publicMessage,errorCode:t.errorCode,classification:t})}async failPrompt(e,t,s,i){await this.terminateRunOutput(e,errorRunOutput(t,s,i))}cleanupAllPendingPrompts(){for(const e of this.state.sessions.values())e.writeGateToken?(this.sessionMutationCoordinator.requireRecovery(e.writeGateToken,"gateway_disconnected","disconnect"),this.observeLifecycle(this.terminateRunOutput(e,{kind:"failed",mode:"interrupted",message:SESSION_RECOVERY_REQUIRED_MESSAGE,errorCode:SESSION_RECOVERY_REQUIRED_CODE}),"gateway_disconnect_recovery",e)):this.observeLifecycle(this.interruptPrompt(e,classifyAgentRuntimeFailure({raw:Errors.GatewayDisconnected.message,source:"live_gateway",runtimeKind:"openclaw"})),"gateway_disconnect_interrupt",e)}handleGatewayResponse(e){const t=e.id;if(!t)return;const s=this.state.promptsByGatewayRequestId.get(t);if(!s||s.promptDone)return;if(!e.ok){const i=e.error,n=i?.message??"gateway returned error";this.logger.warn?.(`[bridge-core] handleGatewayResponse: gateway rejected prompt gatewayRequestId=${t} error=${n}`);const o=classifyAgentRuntimeFailure({raw:n,source:"live_gateway",runtimeKind:"openclaw"});return void("interrupted"===o.kind?this.observeLifecycle(this.interruptPrompt(s,o),"gateway_response_interrupt",s):(this.logRuntimeFailure(s,o),this.observeLifecycle(this.failPrompt(s,Errors.SendFailed.rpcCode,o.publicMessage,o),"gateway_response_failure",s)))}const i=e.payload,n=i?.runId;if(n)s.currentRunId=n,this.sessionMutationCoordinator.bindRun(s.writeGateToken,n),this.observeActiveRun(s),s.assistantStreamStartedAt=Date.now(),this.state.promptsByRunId.set(n,s),openRunOutputRoute(this.runOutputRouter,s,n,["acp"]),this.logger.debug?.(`[bridge-core] handleGatewayResponse: bound runId=${n} gatewayRequestId=${t} sessionId=${s.sessionId}`);else{const e=resolveTurnId(s);e&&openRunOutputRoute(this.runOutputRouter,s,e,["acp"]),this.logger.debug?.(`[bridge-core] handleGatewayResponse: no runId in payload gatewayRequestId=${t} sessionId=${s.sessionId}`)}}async handleCronRequest(e){const t=this.getGateway();if(!t?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);const s=e.method.replace("/","."),i={...e.params??{}};["cron.add","cron.update","cron.remove","cron.run","cron.runs"].includes(s)&&(i.__beeosCronIngress="acp"),"cron.update"===s&&(i.patch=i.patch&&"object"==typeof i.patch?{...i.patch}:{});try{const t=await this.dispatchLegacyCronCapability(s,i),n=await this.mapCronResultForAcp(s,t);this.transport.sendResult(e.id,n??{}),"cron.add"!==s&&"cron.update"!==s&&"cron.remove"!==s||this.refreshCronIsolatedSessionKeys()}catch(t){const i=t instanceof Error?t.message:"cron request failed";this.logger.warn?.(`[bridge-core] ${s} failed: ${i}`),this.transport.sendAcpError(e.id,Errors.SendFailed,{message:i})}}async mapCronResultForAcp(e,t){if(!t||"object"!=typeof t)return t;const s=t;await this.cronIdentities.snapshot();const i=e=>{if(!e||"object"!=typeof e)return e;const t=e,s=t.id;if("string"!=typeof s)return e;const i=this.cronIdentities.localId(s)??s,n=this.cronChannelCache.get(i),o=n?encodeBeeosChannelTag("string"==typeof t.description?t.description:void 0,n):t.description;return{...t,id:i,...void 0===o?{}:{description:o}}};if(Array.isArray(s.jobs))return{...s,jobs:s.jobs.map(i)};if(s.jobs&&"object"==typeof s.jobs){const e=s.jobs,t={};for(const[s,n]of Object.entries(e))t[s]=i(n);return{...s,jobs:t}}return Array.isArray(s.runs)?{...s,runs:s.runs.map(e=>{if(!e||"object"!=typeof e)return e;const t=e,s="string"==typeof t.jobId?this.cronIdentities.localId(t.jobId)??t.jobId:t.jobId;return{...t,jobId:s}})}:"cron.add"===e||"cron.update"===e?i(t):t}async allocateCronChannel(e,t){if(!this.cloud)return void this.logger.debug?.("[bridge-core] Cloud gateway unavailable, skip channel alloc");const s=this.getAuthoritativeInstanceId?.()?.trim();if(!s?.startsWith("inst_"))return void this.logger.warn?.("[bridge-core] no authoritative inst_* instance id resolved yet, skip channel alloc");const i=this.resolvePlatformAgentId(t)?.trim();if(!i)return void this.logger.warn?.(`[bridge-core] no platform agent id for runtimeLocalKey=${t}, skip channel alloc`);const n={source:"cron",job_name:e,protocol:"beeos-conversation",target_agent_id:i,title:e};try{return(await this.cloud.channels.create({participants:[s,"service:cron"],metadata:n})).id}catch(e){return void this.logger.warn?.(`[bridge-core] channel alloc error: ${e instanceof Error?e.message:e}`)}}setCronChannelCache(e,t){if(this.cronChannelCache.size>=AcpGatewayBridgeCore.CRON_CHANNEL_CACHE_MAX){const e=this.cronChannelCache.keys().next().value;void 0!==e&&this.cronChannelCache.delete(e)}this.cronChannelCache.set(e,t)}async refreshCronIsolatedSessionKeys(){const e=this.getGateway();if(e?.isConnected)try{const t=await e.request("cron.list",{}),s=t?.jobs;if(!Array.isArray(s))return;const i=new Set,n=[];for(const e of s){if(!e||"object"!=typeof e)continue;const t=e;if("string"==typeof t.id&&n.push(t.id),"isolated"!==t.sessionTarget)continue;const s=t.sessionKey;"string"==typeof s&&s.length>0&&i.add(s)}this.cronIsolatedSessionKeys=i,await this.cronIdentities.reconcile(n)}catch(e){this.logger.debug?.(`[bridge-core] refreshCronIsolatedSessionKeys failed: ${e instanceof Error?e.message:e}`)}}}async function fetchWithRetry(e,t={}){const s=t.maxRetries??3;let i;for(let n=0;n<=s;n++){const o=await fetch(e,{signal:AbortSignal.timeout(12e4),redirect:"follow"});if(o.ok)return Buffer.from(await o.arrayBuffer());if(429===o.status||o.status>=500){const r=Number(o.headers.get("retry-after"))||0,a=1e3*Math.max(r,2**n);if(i=new Error(`HTTP ${o.status} from ${e}`),n<s){t.logger?.info?.(`[bridge-core] Download ${o.status}, retry ${n+1}/${s} in ${(a/1e3).toFixed(0)}s`),await new Promise(e=>setTimeout(e,a));continue}}throw new Error(`Failed to download ${e}: HTTP ${o.status}`)}throw i??new Error(`Download failed after retries: ${e}`)}const SKILL_ROOT_MARKERS=["SKILL.md","skill.md","SKILL.MD"];function normalizeSkillArtifactPath(e){const t=e.replaceAll("\\","/").replace(/^\.\//,"");if(!t||t.startsWith("/")||t.split("/").includes(".."))throw new RuntimeCommandError("INVALID_PARAMS",`Invalid skill artifact path: ${e}`);return t}function parseSkillZip(e){if(e.byteLength>MAX_ZIP_BYTES)throw new RuntimeCommandError("INVALID_PARAMS","Skill archive too large");const t=new AdmZip(e).getEntries().filter(e=>!e.isDirectory).map(e=>({name:normalizeSkillArtifactPath(e.entryName),bytes:e.getData()})).filter(e=>!e.name.split("/").includes("__MACOSX"));if(0===t.length)throw new RuntimeCommandError("INVALID_PARAMS","Skill archive is empty");const s=1===new Set(t.map(e=>e.name.split("/")[0])).size&&t.every(e=>e.name.includes("/")),i=new Map;for(const e of t){const t=s?e.name.slice(e.name.indexOf("/")+1):e.name;if(!t||i.has(t))throw new RuntimeCommandError("INVALID_PARAMS",`Duplicate skill path: ${t}`);i.set(t,e.bytes)}return i}async function resolveSkillRoot(e){for(const t of SKILL_ROOT_MARKERS)try{return await stat(join(e,t)),e}catch{}const t=(await readdir(e,{withFileTypes:!0})).filter(e=>e.isDirectory()&&!e.name.startsWith(".")&&"__MACOSX"!==e.name);if(1===t.length){const s=join(e,t[0].name);for(const e of SKILL_ROOT_MARKERS)try{return await stat(join(s,e)),s}catch{}return s}return e}async function extractZipToDir(e,t){const s=new AdmZip(e),i=join(t,".beeos-extract-tmp-"+Date.now());try{await mkdir(i,{recursive:!0}),s.extractAllTo(i,!0);const e=await resolveSkillRoot(i),n=await readdir(e,{withFileTypes:!0});let o=0;for(const s of n){if(s.name.startsWith(".beeos-extract-tmp")||"__MACOSX"===s.name)continue;const i=join(e,s.name),n=join(t,s.name);await rename(i,n).catch(async()=>{await cp(i,n,{recursive:!0})}),o++}if(0===o)throw new Error("Zip archive contained no extractable files")}finally{await rm(i,{recursive:!0,force:!0}).catch(()=>{})}}const VALID_SLUG_PATTERN=/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,NON_ASCII_PATTERN=/[^\x00-\x7F]/;function validateSkillSlug(e){const t=e.trim();if(!t||t.includes("/")||t.includes("\\")||t.includes(".."))throw new Error(`Invalid skill slug: ${e}`);if(NON_ASCII_PATTERN.test(t)||!VALID_SLUG_PATTERN.test(t))throw new Error(`Invalid skill slug: ${e}`);return t}function safeDirName(e){const t=e.trim();return t?t.replaceAll("/","__").replaceAll("\\","__"):t}function extractMcpCredentialMaterial(e){const t=mcpStringMap(e.headers,"MCP headers"),s=mcpStringMap(e.env,"MCP environment");if(!(t&&0!==Object.keys(t).length||s&&0!==Object.keys(s).length))return;const i=e.transport??("string"==typeof e.command?"stdio":"string"==typeof e.url?"streamable-http":void 0);if("stdio"===i&&t&&Object.keys(t).length)throw new Error("MCP stdio config cannot include headers");if("stdio"!==i&&s&&Object.keys(s).length)throw new Error("MCP HTTP config cannot include env");return{...t&&Object.keys(t).length?{headers:t}:{},...s&&Object.keys(s).length?{env:s}:{}}}function mcpStringMap(e,t){if(void 0!==e){if(!e||"object"!=typeof e||Array.isArray(e)||Object.entries(e).some(([e,t])=>!e||/[\u0000-\u001f\u007f]/.test(e)||"string"!=typeof t))throw new Error(`${t} invalid`);return{...e}}}function requireCredentialReferencePort(e){if(!e)throw new Error("MCP credential reference vault is unavailable");return e}function resolveSkillInstallDir(e,t){const s=safeDirName(t),i=join(e,s),n=resolve(e),o=resolve(i),r=relative(n,o);if(!r||".."===r||r.startsWith(`..${sep}`)||path.isAbsolute(r))throw new Error("Invalid skill target path");return i}
@@ -1 +1 @@
1
- import{extractBeeosConversationIdFromSessionKey}from"../runtime/openclaw-session-key-resolution.js";const A2UI_TYPE_NAMES=new Set(["Text","Image","Icon","Video","AudioPlayer","Row","Column","List","Card","Tabs","Divider","Modal","Button","CheckBox","TextField","DateTimeInput","MultipleChoice","Slider","Chart","WebEmbed","FilePreview","MapView"]);function normalizeComponents(e){return e.map(e=>{if(!e||"object"!=typeof e)return e;const t=e;if(t.component&&"object"==typeof t.component)return t;for(const e of Object.keys(t))if(A2UI_TYPE_NAMES.has(e)&&"object"==typeof t[e])return{id:t.id,component:{[e]:t[e]}};return t})}export const RENDER_UI_TOOL_NAME="render_ui";export const CLOSE_UI_TOOL_NAME="close_ui";export const RENDER_UI_TOOL_DESCRIPTION="Render A2UI v0.8 components to the Canvas panel. Prefer this over the built-in canvas tool.";export const RENDER_UI_TOOL_SCHEMA={type:"object",required:["id","root","components"],properties:{id:{type:"string",description:"Surface id. Reuse to update."},root:{type:"string",description:"Root component id."},components:{type:"array",description:"Flat ComponentInstance list. Each item has id plus one component type.",items:{type:"object",required:["id"],properties:{id:{type:"string",description:"Component id."},component:{type:"object",description:"One type key: Text, Image, Icon, Video, AudioPlayer, Row, Column, List, Card, Tabs, Divider, Modal, Button, CheckBox, TextField, DateTimeInput, MultipleChoice, Slider, Chart."}}}},data:{type:"object",description:"Optional binding model, path keys like /name."}}};export const CLOSE_UI_TOOL_DESCRIPTION="Close and remove a UI surface that was previously rendered.";export const CLOSE_UI_TOOL_SCHEMA={type:"object",required:["id"],properties:{id:{type:"string",description:"The UI identifier to close"}}};const SESSION_READY_POLL_ATTEMPTS=3,SESSION_READY_POLL_INTERVAL_MS=400,delay=e=>new Promise(t=>setTimeout(t,e));async function ensureRelayConnected(e,t,n,a){const{stateManager:o,logger:i}=e,r=a??e.getGatewaySessionKey?.(n),s=r??t,l=o.resolveCanvasForSession(s);if(l&&o.isCanvasJoined(l))return l;let c=o.getRelayClient();if(c||(c=await o.waitForRelayClient(),c))try{e.waitForAgentSync&&await e.waitForAgentSync();const a=e.getPlatformAgentId?.(n),l=r??t,d=(r?extractBeeosConversationIdFromSessionKey(r):void 0)??(r&&r!==t?t:void 0),u=await c.ensureCanvas(l,{agentId:a,linkSessionId:d});return await o.joinCanvas(u,s),i.info?.(`[canvas] Joined canvas=${u} session=${t} gatewayKey=${r??"none"} agentId=${a??"none"}`),u}catch(e){return void i.warn?.(`[canvas] Failed to join canvas relay: ${e}`)}}export function createRenderUiHandler(e){const{stateManager:t,logger:n,writeObs:a,getGatewaySessionKey:o}=e;let i=0;return async(o,r)=>{const s=r,l=s.id,c=s.root,d="panel";let u=s.components;if("string"==typeof u)try{u=JSON.parse(u)}catch{}if(!(l&&c&&Array.isArray(u)&&u.length))return{success:!1,error:"id, root, and components (array) are required"};const p=normalizeComponents(u);if(!t.validateComponentCount(p))return{success:!1,error:"Too many components (max 500)"};if(!t.checkRateLimit(l))return n.warn?.(`[canvas] Rate limited surfaceId=${l}`),a({component:"canvas",domain:"canvas",name:"canvas.rate_limited",severity:"warn",payload:{surfaceId:l}}),{success:!1,error:"Rate limited — too many updates per second"};const m=s.data;let h=e.getActiveSessionId?.(o);for(let t=0;!h&&t<3;t++)await delay(400),h=e.getActiveSessionId?.(o);if(!h&&e.waitForAgentSync){try{await e.waitForAgentSync()}catch{}h=e.getActiveSessionId?.(o)}if(!h)return n.warn?.(`[canvas] No active session for toolCallId=${o} after 3 polls, render_ui not ready`),{success:!1,retryable:!0,error:"Canvas session not ready yet (agent just connected) — retry render_ui shortly; do NOT fall back to plain text."};const y=e.getGatewaySessionKey?.(o),f=await ensureRelayConnected(e,h,o,y),g=t.has(l,f??void 0),v=Date.now(),C=t.getRelayClient();f&&C&&v-i>6e4&&C.createSnapshot(f,"Pre-agent edit").then(()=>{i=Date.now(),n.debug?.("[canvas] Pre-edit checkpoint created")}).catch(e=>n.warn?.(`[canvas] Pre-edit checkpoint failed: ${e}`)),g?t.update(l,p,f,c,m):t.create(f??`_fallback_${Date.now()}`,l,d,p,{origin:{kind:"agent"},rootComponentId:c,dataModel:m});const b=p.map(e=>e.component&&"object"==typeof e.component?Object.keys(e.component)[0]??"unknown":"unknown");a({component:"canvas",domain:"canvas",name:g?"canvas.update_sent":"canvas.surface_created",severity:"info",payload:{surfaceId:l,presentation:d,componentCount:p.length,componentTypes:b}}),n.debug?.(`[canvas] ${g?"Updated":"Created"} surface=${l} components=${p.length}`);const w={success:!0,surfaceId:l,presentation:d,isUpdate:g,activeSurfaces:f?t.getAllActive(f).map(e=>e.surfaceId):[l]};return m&&(w.dataModel=m),w}}export function createCloseUiHandler(e){const{stateManager:t,logger:n,writeObs:a}=e;return async(o,i)=>{const r=i.id;if(!r)return{success:!1,error:"id is required"};const s=t.get(r),l=s?Date.now()-s.createdAt:0,c=e.getGatewaySessionKey?.(o),d=c?t.resolveCanvasForSession(c):void 0;return t.delete(r,d),a({component:"canvas",domain:"canvas",name:"canvas.surface_closed",severity:"info",payload:{surfaceId:r,lifetime_ms:l}}),n.debug?.(`[canvas] Closed surface=${r}`),{success:!0,surfaceId:r,message:"UI closed"}}}export const CANVAS_PRESENTATION_GATE="## BeeOS Canvas presentation gate\n\nCanvas presentation is available in the background for this BeeOS conversation. The Canvas resource tab does not need to be open and no Web subscriber is required before a Canvas tool is called. Describe successful rendering as content created or updated on the canvas; do not claim that a user is currently viewing it.\n\nDo not read `beeos-canvas/SKILL.md` or any canvas skill file. Call `render_ui` directly. The A2UI schema is in this prompt and the `render_ui` tool.\n\nAfter any skill or data tool returns, apply this gate before writing the final reply:\n1. Count the related records, items, rows, time points, options, or repeated field groups in the completed result.\n2. If there are two or more, you MUST call `render_ui` before the final reply.\n3. Do not replace `render_ui` with a Markdown table, bullets, JSON, or a prose summary.\n4. If there is only one fact or value, or the answer is a prose explanation, reply in chat without `render_ui`.\n\nDomain skills control how data is obtained and interpreted. This gate controls how the completed result is presented and overrides a domain skill's default text-summary format. When `render_ui` succeeds, keep the chat reply to one short confirmation sentence.";export const CANVAS_SYSTEM_PROMPT='## Canvas UI — Google A2UI v0.8\n\n**IMPORTANT: Always use `render_ui` (not the built-in `canvas` tool) to display visual/structured content.**\n\n### How render_ui works\n\nYou output a **flat array of components** with unique IDs. Components reference each other by ID.\n\nParameters:\n- `id`: Surface ID (e.g. "sales-report"). Reuse same id to update.\n- `root`: The ID of the top-level component (usually a Column).\n- `components`: Flat array of objects. **Each object MUST have `id` and `component`**:\n `{"id": "myId", "component": {"Text": {"text": {"literalString": "Hello"}, "usageHint": "body"}}}`\n The `component` key wraps exactly ONE type key (Text, Card, Row, etc.) and its properties.\n- `data` (optional): Data model object for form two-way binding.\n\n### Rules\n- Keep chat reply to ONE short sentence when using render_ui. Do NOT repeat data shown in canvas.\n- Use render_ui for structured data, visualizations, forms, dashboards. Use plain text for explanations.\n- Canvas renders in a narrow sidebar (~400px).\n- Prefer fewer, larger components. One table is better than many small cards.\n- Use descriptive surface IDs (e.g. "sales-report", not "ui1").\n- Same `id` updates existing surface; different `id` creates new one.\n- When asked to modify existing content, reuse the original surface id from [Canvas State].\n\n### StringValue\n\nText properties use StringValue — an object with exactly one key:\n- Static text: `{"literalString": "Hello"}`\n- Data binding: `{"path": "/fieldName"}` (reads from the `data` model)\n\nShorthand: `sv("text")` means `{"literalString": "text"}` in examples below.\n\n### Component Reference\n\n**Text** — Display text with semantic sizing.\n`{Text: {text: sv("Title"), usageHint: "h2"}}`\nusageHint: "h1" | "h2" | "h3" | "h4" | "h5" | "caption" | "body"\ntext also renders Markdown (bold, italic, lists, code blocks, links).\n\n**Image** — Display an image.\n`{Image: {url: sv("https://..."), usageHint: "mediumFeature"}}`\nImage.url may be `beeos-file://<fileId>`; the renderer resolves it. Do not convert it to a signed URL.\nusageHint: "icon" | "avatar" | "smallFeature" | "mediumFeature" | "largeFeature" | "header"\nfit (optional): "contain" | "cover" | "fill" | "none" | "scale-down"\n\n**Icon** — Material icon.\n`{Icon: {name: sv("check")}}`\n\n**Video** — Embed a video.\n`{Video: {url: sv("https://youtube.com/...")}}`\n\n**AudioPlayer** — Audio playback.\n`{AudioPlayer: {url: sv("https://..."), description: sv("Episode 1")}}`\n\n**Row** — Horizontal layout. Children referenced by ID.\n`{Row: {children: {explicitList: ["child1", "child2"]}, distribution: "spaceBetween"}}`\ndistribution: "start" | "center" | "end" | "spaceBetween" | "spaceAround" | "spaceEvenly"\nalignment: "start" | "center" | "end" | "stretch"\n\n**Column** — Vertical layout. Children referenced by ID.\n`{Column: {children: {explicitList: ["child1", "child2"]}}}`\nSame distribution/alignment options as Row.\n\n**List** — Vertical or horizontal list.\n`{List: {children: {explicitList: ["item1", "item2"]}, direction: "vertical"}}`\n\n**Card** — Container with a single child.\n`{Card: {child: "contentColumnId"}}`\n\n**Tabs** — Tabbed container.\n`{Tabs: {tabItems: [{title: sv("Tab 1"), child: "panel1"}, {title: sv("Tab 2"), child: "panel2"}]}}`\n\n**Divider** — Horizontal or vertical separator.\n`{Divider: {axis: "horizontal"}}`\n\n**Modal** — Dialog with trigger.\n`{Modal: {entryPointChild: "triggerBtnId", contentChild: "dialogContentId"}}`\n\n**Button** — Clickable with action.\n`{Button: {child: "labelTextId", action: {name: "onClick", context: [{key: "id", value: {literalString: "item-1"}}]}}}`\n\n**TextField** — Text input with data binding.\n`{TextField: {label: sv("Name"), text: {path: "/name"}, type: "shortText"}}`\ntype: "shortText" | "number" | "date" | "longText"\n\n**CheckBox** — Boolean toggle.\n`{CheckBox: {label: sv("Agree"), value: {path: "/agree"}}}`\n\n**DateTimeInput** — Date/time picker.\n`{DateTimeInput: {value: {path: "/date"}, enableDate: true, enableTime: false}}`\n\n**MultipleChoice** — Selection from options.\n`{MultipleChoice: {selections: {path: "/country"}, options: [{label: sv("US"), value: "US"}, {label: sv("CN"), value: "CN"}]}}`\n\n**Slider** — Numeric range input.\n`{Slider: {value: {path: "/volume"}, minValue: 0, maxValue: 100}}`\n\n**Chart** (BeeOS extension) — Data visualization.\n`{Chart: {chartType: sv("bar"), title: sv("Sales"), labels: ["Q1","Q2","Q3"], datasets: [{label: "Revenue", data: [100,200,150]}]}}`\nchartType: bar, horizontal_bar, line, area, scatter, pie, doughnut, radar, gauge, candlestick, funnel, sparkline\ngauge uses value + max instead of labels/datasets.\ncandlestick data: [{open,high,low,close}] (full field names, NOT o/h/l/c).\n\n### Composition\n\nNest by id: Column/Row `children.explicitList`, Card `child`. Example:\n`{"id":"s1","root":"r","components":[{"id":"t","component":{"Text":{"text":{"literalString":"Hello"},"usageHint":"h2"}}},{"id":"r","component":{"Column":{"children":{"explicitList":["t"]}}}}]}`\nTables: Card > Column > Row > Text. Forms: TextField/CheckBox + Button `action.context` paths.\n\n### render_form\nUse `render_form` for schema-driven forms when a domain schema exists.\nParameters: `domain`, `operation`, `data`, `entityId`.\n\n### close_ui\nCloses a UI surface. Use when user moves to a new topic or explicitly dismisses.\n';export const CANVAS_TERM_MAPPING='[System Note] Always use `render_ui` (not the canvas tool) for visual/structured content. Each component in the array MUST be: {"id":"x","component":{"TypeName":{props}}} — the "component" wrapper key is required. render_ui uses Google A2UI v0.8 format: flat component array with id references. Components: Text (usageHint: h1-h5/caption/body), Image, Icon, Video, AudioPlayer, Row (horizontal layout, children.explicitList), Column (vertical layout, children.explicitList), List, Card (single child), Tabs (tabItems), Divider, Modal, Button (child + action), TextField, CheckBox, MultipleChoice, Slider, DateTimeInput, Chart (BeeOS extension: chartType, labels, datasets). StringValue: {"literalString":"text"} or {"path":"/binding"}. root param = top-level component ID. Tables: Card > Column > Row > Text. Forms: TextField/CheckBox/Slider + Button with action.context paths. Chart: chartType(bar/line/pie/area/scatter/doughnut/radar/funnel/gauge/candlestick/sparkline), labels, datasets:[{label,data,color?}]. Candlestick: [{open,high,low,close}]. Gauge: value+max. Keep chat brief when canvas is used. Use `close_ui` to dismiss. Reuse same id to update; new id for new content.';export const CANVAS_UPDATE_HINT="[Canvas Rule] Same id in render_ui = update existing surface. Different id = new surface. When a surface is referenced for editing, you MUST reuse its exact id and provide the full updated component tree.";const MAX_CONTEXT_SURFACES=8,MAX_CONTEXT_CHARS=500;export function buildActiveSurfacesContext(e,t){let n;if(!t)return null;if(n=e.getAllActive(t),0===n.length)return null;const a=[...n].sort((e,t)=>t.updatedAt-e.updatedAt).slice(0,8),o=n.length-a.length;let i=`[Canvas State] Active surfaces: ${a.map(t=>{const n=e.summarize(t.surfaceId);if(!n)return`"${t.surfaceId}"`;const a=n.types.slice(0,3).join(", "),o=n.title?`: "${n.title}"`:"";return`"${t.surfaceId}" (${a}${o})`}).join("; ")}.`;return o>0&&(i+=` ...and ${o} more.`),i+=" To modify existing content, reuse its id in render_ui.",i.length>500&&(i=i.slice(0,497)+"..."),i}
1
+ import{extractBeeosConversationIdFromSessionKey}from"../runtime/openclaw-session-key-resolution.js";const A2UI_TYPE_NAMES=new Set(["Text","Image","Icon","Video","AudioPlayer","Row","Column","List","Card","Tabs","Divider","Modal","Button","CheckBox","TextField","DateTimeInput","MultipleChoice","Slider","Chart","WebEmbed","FilePreview","MapView"]);function normalizeComponents(e){return e.map(e=>{if(!e||"object"!=typeof e)return e;const t=e;if(t.component&&"object"==typeof t.component)return t;for(const e of Object.keys(t))if(A2UI_TYPE_NAMES.has(e)&&"object"==typeof t[e])return{id:t.id,component:{[e]:t[e]}};return t})}export const RENDER_UI_TOOL_NAME="render_ui";export const CLOSE_UI_TOOL_NAME="close_ui";export const CANVAS_COMPONENT_ID_GUIDANCE="Component IDs must differ from non-reference scalar values (usageHint, literalString, and action.name). Use type-qualified IDs such as text_heading, text_body, and column_root; do not use scalar values such as body, h1, or center as IDs. A2UI v0.8 resolves strings matching component IDs as references. Keep root, child, explicitList, and template.componentId references matching their target IDs. Reuse the same component IDs when updating a surface.";export const RENDER_UI_TOOL_DESCRIPTION="Render A2UI v0.8 components to the Canvas panel. Prefer this over the built-in canvas tool.";export const RENDER_UI_TOOL_SCHEMA={type:"object",required:["id","root","components"],properties:{id:{type:"string",description:"Surface id. Reuse to update."},root:{type:"string",description:"Root component id."},components:{type:"array",description:"Flat ComponentInstance list. Each item has id plus one component type.",items:{type:"object",required:["id"],properties:{id:{type:"string",description:CANVAS_COMPONENT_ID_GUIDANCE},component:{type:"object",description:"One type key: Text, Image, Icon, Video, AudioPlayer, Row, Column, List, Card, Tabs, Divider, Modal, Button, CheckBox, TextField, DateTimeInput, MultipleChoice, Slider, Chart."}}}},data:{type:"object",description:"Optional binding model, path keys like /name."}}};export const CLOSE_UI_TOOL_DESCRIPTION="Close and remove a UI surface that was previously rendered.";export const CLOSE_UI_TOOL_SCHEMA={type:"object",required:["id"],properties:{id:{type:"string",description:"The UI identifier to close"}}};const SESSION_READY_POLL_ATTEMPTS=3,SESSION_READY_POLL_INTERVAL_MS=400,delay=e=>new Promise(t=>setTimeout(t,e));async function ensureRelayConnected(e,t,n,a){const{stateManager:o,logger:i}=e,r=a??e.getGatewaySessionKey?.(n),s=r??t,l=o.resolveCanvasForSession(s);if(l&&o.isCanvasJoined(l))return l;let c=o.getRelayClient();if(c||(c=await o.waitForRelayClient(),c))try{e.waitForAgentSync&&await e.waitForAgentSync();const a=e.getPlatformAgentId?.(n),l=r??t,d=(r?extractBeeosConversationIdFromSessionKey(r):void 0)??(r&&r!==t?t:void 0),u=await c.ensureCanvas(l,{agentId:a,linkSessionId:d});return await o.joinCanvas(u,s),i.info?.(`[canvas] Joined canvas=${u} session=${t} gatewayKey=${r??"none"} agentId=${a??"none"}`),u}catch(e){return void i.warn?.(`[canvas] Failed to join canvas relay: ${e}`)}}export function createRenderUiHandler(e){const{stateManager:t,logger:n,writeObs:a,getGatewaySessionKey:o}=e;let i=0;return async(o,r)=>{const s=r,l=s.id,c=s.root,d="panel";let u=s.components;if("string"==typeof u)try{u=JSON.parse(u)}catch{}if(!(l&&c&&Array.isArray(u)&&u.length))return{success:!1,error:"id, root, and components (array) are required"};const p=normalizeComponents(u);if(!t.validateComponentCount(p))return{success:!1,error:"Too many components (max 500)"};if(!t.checkRateLimit(l))return n.warn?.(`[canvas] Rate limited surfaceId=${l}`),a({component:"canvas",domain:"canvas",name:"canvas.rate_limited",severity:"warn",payload:{surfaceId:l}}),{success:!1,error:"Rate limited — too many updates per second"};const m=s.data;let h=e.getActiveSessionId?.(o);for(let t=0;!h&&t<3;t++)await delay(400),h=e.getActiveSessionId?.(o);if(!h&&e.waitForAgentSync){try{await e.waitForAgentSync()}catch{}h=e.getActiveSessionId?.(o)}if(!h)return n.warn?.(`[canvas] No active session for toolCallId=${o} after 3 polls, render_ui not ready`),{success:!1,retryable:!0,error:"Canvas session not ready yet (agent just connected) — retry render_ui shortly; do NOT fall back to plain text."};const y=e.getGatewaySessionKey?.(o),f=await ensureRelayConnected(e,h,o,y),g=t.has(l,f??void 0),v=Date.now(),C=t.getRelayClient();f&&C&&v-i>6e4&&C.createSnapshot(f,"Pre-agent edit").then(()=>{i=Date.now(),n.debug?.("[canvas] Pre-edit checkpoint created")}).catch(e=>n.warn?.(`[canvas] Pre-edit checkpoint failed: ${e}`)),g?t.update(l,p,f,c,m):t.create(f??`_fallback_${Date.now()}`,l,d,p,{origin:{kind:"agent"},rootComponentId:c,dataModel:m});const b=p.map(e=>e.component&&"object"==typeof e.component?Object.keys(e.component)[0]??"unknown":"unknown");a({component:"canvas",domain:"canvas",name:g?"canvas.update_sent":"canvas.surface_created",severity:"info",payload:{surfaceId:l,presentation:d,componentCount:p.length,componentTypes:b}}),n.debug?.(`[canvas] ${g?"Updated":"Created"} surface=${l} components=${p.length}`);const I={success:!0,surfaceId:l,presentation:d,isUpdate:g,activeSurfaces:f?t.getAllActive(f).map(e=>e.surfaceId):[l]};return m&&(I.dataModel=m),I}}export function createCloseUiHandler(e){const{stateManager:t,logger:n,writeObs:a}=e;return async(o,i)=>{const r=i.id;if(!r)return{success:!1,error:"id is required"};const s=t.get(r),l=s?Date.now()-s.createdAt:0,c=e.getGatewaySessionKey?.(o),d=c?t.resolveCanvasForSession(c):void 0;return t.delete(r,d),a({component:"canvas",domain:"canvas",name:"canvas.surface_closed",severity:"info",payload:{surfaceId:r,lifetime_ms:l}}),n.debug?.(`[canvas] Closed surface=${r}`),{success:!0,surfaceId:r,message:"UI closed"}}}export const CANVAS_PRESENTATION_GATE="## BeeOS Canvas presentation gate\n\nCanvas presentation is available in the background for this BeeOS conversation. The Canvas resource tab does not need to be open and no Web subscriber is required before a Canvas tool is called. Describe successful rendering as content created or updated on the canvas; do not claim that a user is currently viewing it.\n\nDo not read `beeos-canvas/SKILL.md` or any canvas skill file. Call `render_ui` directly. The A2UI schema is in this prompt and the `render_ui` tool.\n\nAfter any skill or data tool returns, apply this gate before writing the final reply:\n1. Count the related records, items, rows, time points, options, or repeated field groups in the completed result.\n2. If there are two or more, you MUST call `render_ui` before the final reply.\n3. Do not replace `render_ui` with a Markdown table, bullets, JSON, or a prose summary.\n4. If there is only one fact or value, or the answer is a prose explanation, reply in chat without `render_ui`.\n\nDomain skills control how data is obtained and interpreted. This gate controls how the completed result is presented and overrides a domain skill's default text-summary format. When `render_ui` succeeds, keep the chat reply to one short confirmation sentence.";export const CANVAS_SYSTEM_PROMPT=`## Canvas UI — Google A2UI v0.8\n\n**IMPORTANT: Always use \`render_ui\` (not the built-in \`canvas\` tool) to display visual/structured content.**\n\n### How render_ui works\n\nYou output a **flat array of components** with unique IDs. Components reference each other by ID.\n${CANVAS_COMPONENT_ID_GUIDANCE}\n\nParameters:\n- \`id\`: Surface ID (e.g. "sales-report"). Reuse same id to update.\n- \`root\`: The ID of the top-level component (usually a Column).\n- \`components\`: Flat array of objects. **Each object MUST have \`id\` and \`component\`**:\n \`{"id": "myId", "component": {"Text": {"text": {"literalString": "Hello"}, "usageHint": "body"}}}\`\n The \`component\` key wraps exactly ONE type key (Text, Card, Row, etc.) and its properties.\n- \`data\` (optional): Data model object for form two-way binding.\n\n### Rules\n- Keep chat reply to ONE short sentence when using render_ui. Do NOT repeat data shown in canvas.\n- Use render_ui for structured data, visualizations, forms, dashboards. Use plain text for explanations.\n- Canvas renders in a narrow sidebar (~400px).\n- Prefer fewer, larger components. One table is better than many small cards.\n- Use descriptive surface IDs (e.g. "sales-report", not "ui1").\n- Same \`id\` updates existing surface; different \`id\` creates new one.\n- When asked to modify existing content, reuse the original surface id from [Canvas State].\n\n### StringValue\n\nText properties use StringValue — an object with exactly one key:\n- Static text: \`{"literalString": "Hello"}\`\n- Data binding: \`{"path": "/fieldName"}\` (reads from the \`data\` model)\n\nShorthand: \`sv("text")\` means \`{"literalString": "text"}\` in examples below.\n\n### Component Reference\n\n**Text** — Display text with semantic sizing.\n\`{Text: {text: sv("Title"), usageHint: "h2"}}\`\nusageHint: "h1" | "h2" | "h3" | "h4" | "h5" | "caption" | "body"\ntext also renders Markdown (bold, italic, lists, code blocks, links).\n\n**Image** — Display an image.\n\`{Image: {url: sv("https://..."), usageHint: "mediumFeature"}}\`\nImage.url may be \`beeos-file://<fileId>\`; the renderer resolves it. Do not convert it to a signed URL.\nusageHint: "icon" | "avatar" | "smallFeature" | "mediumFeature" | "largeFeature" | "header"\nfit (optional): "contain" | "cover" | "fill" | "none" | "scale-down"\n\n**Icon** — Material icon.\n\`{Icon: {name: sv("check")}}\`\n\n**Video** — Embed a video.\n\`{Video: {url: sv("https://youtube.com/...")}}\`\n\n**AudioPlayer** — Audio playback.\n\`{AudioPlayer: {url: sv("https://..."), description: sv("Episode 1")}}\`\n\n**Row** — Horizontal layout. Children referenced by ID.\n\`{Row: {children: {explicitList: ["child1", "child2"]}, distribution: "spaceBetween"}}\`\ndistribution: "start" | "center" | "end" | "spaceBetween" | "spaceAround" | "spaceEvenly"\nalignment: "start" | "center" | "end" | "stretch"\n\n**Column** — Vertical layout. Children referenced by ID.\n\`{Column: {children: {explicitList: ["child1", "child2"]}}}\`\nSame distribution/alignment options as Row.\n\n**List** — Vertical or horizontal list.\n\`{List: {children: {explicitList: ["item1", "item2"]}, direction: "vertical"}}\`\n\n**Card** — Container with a single child.\n\`{Card: {child: "contentColumnId"}}\`\n\n**Tabs** — Tabbed container.\n\`{Tabs: {tabItems: [{title: sv("Tab 1"), child: "panel1"}, {title: sv("Tab 2"), child: "panel2"}]}}\`\n\n**Divider** — Horizontal or vertical separator.\n\`{Divider: {axis: "horizontal"}}\`\n\n**Modal** — Dialog with trigger.\n\`{Modal: {entryPointChild: "triggerBtnId", contentChild: "dialogContentId"}}\`\n\n**Button** — Clickable with action.\n\`{Button: {child: "labelTextId", action: {name: "onClick", context: [{key: "id", value: {literalString: "item-1"}}]}}}\`\n\n**TextField** — Text input with data binding.\n\`{TextField: {label: sv("Name"), text: {path: "/name"}, type: "shortText"}}\`\ntype: "shortText" | "number" | "date" | "longText"\n\n**CheckBox** — Boolean toggle.\n\`{CheckBox: {label: sv("Agree"), value: {path: "/agree"}}}\`\n\n**DateTimeInput** — Date/time picker.\n\`{DateTimeInput: {value: {path: "/date"}, enableDate: true, enableTime: false}}\`\n\n**MultipleChoice** — Selection from options.\n\`{MultipleChoice: {selections: {path: "/country"}, options: [{label: sv("US"), value: "US"}, {label: sv("CN"), value: "CN"}]}}\`\n\n**Slider** — Numeric range input.\n\`{Slider: {value: {path: "/volume"}, minValue: 0, maxValue: 100}}\`\n\n**Chart** (BeeOS extension) — Data visualization.\n\`{Chart: {chartType: sv("bar"), title: sv("Sales"), labels: ["Q1","Q2","Q3"], datasets: [{label: "Revenue", data: [100,200,150]}]}}\`\nchartType: bar, horizontal_bar, line, area, scatter, pie, doughnut, radar, gauge, candlestick, funnel, sparkline\ngauge uses value + max instead of labels/datasets.\ncandlestick data: [{open,high,low,close}] (full field names, NOT o/h/l/c).\n\n### Composition\n\nNest by id: Column/Row \`children.explicitList\`, Card \`child\`. Example:\n\`{"id":"s1","root":"column_root","components":[{"id":"text_body","component":{"Text":{"text":{"literalString":"Hello"},"usageHint":"body"}}},{"id":"column_root","component":{"Column":{"children":{"explicitList":["text_body"]}}}}]}\`\nTables: Card > Column > Row > Text. Forms: TextField/CheckBox + Button \`action.context\` paths.\n\n### render_form\nUse \`render_form\` for schema-driven forms when a domain schema exists.\nParameters: \`domain\`, \`operation\`, \`data\`, \`entityId\`.\n\n### close_ui\nCloses a UI surface. Use when user moves to a new topic or explicitly dismisses.\n`;export const CANVAS_TERM_MAPPING=`[System Note] Always use \`render_ui\` (not the canvas tool) for visual/structured content. Each component in the array MUST be: {"id":"x","component":{"TypeName":{props}}} — the "component" wrapper key is required. render_ui uses Google A2UI v0.8 format: flat component array with id references. ${CANVAS_COMPONENT_ID_GUIDANCE} Components: Text (usageHint: h1-h5/caption/body), Image, Icon, Video, AudioPlayer, Row (horizontal layout, children.explicitList), Column (vertical layout, children.explicitList), List, Card (single child), Tabs (tabItems), Divider, Modal, Button (child + action), TextField, CheckBox, MultipleChoice, Slider, DateTimeInput, Chart (BeeOS extension: chartType, labels, datasets). StringValue: {"literalString":"text"} or {"path":"/binding"}. root param = top-level component ID. Tables: Card > Column > Row > Text. Forms: TextField/CheckBox/Slider + Button with action.context paths. Chart: chartType(bar/line/pie/area/scatter/doughnut/radar/funnel/gauge/candlestick/sparkline), labels, datasets:[{label,data,color?}]. Candlestick: [{open,high,low,close}]. Gauge: value+max. Keep chat brief when canvas is used. Use \`close_ui\` to dismiss. Reuse same id to update; new id for new content.`;export const CANVAS_UPDATE_HINT="[Canvas Rule] Same id in render_ui = update existing surface. Different id = new surface. When a surface is referenced for editing, you MUST reuse its exact id and provide the full updated component tree.";const MAX_CONTEXT_SURFACES=8,MAX_CONTEXT_CHARS=500;export function buildActiveSurfacesContext(e,t){let n;if(!t)return null;if(n=e.getAllActive(t),0===n.length)return null;const a=[...n].sort((e,t)=>t.updatedAt-e.updatedAt).slice(0,8),o=n.length-a.length;let i=`[Canvas State] Active surfaces: ${a.map(t=>{const n=e.summarize(t.surfaceId);if(!n)return`"${t.surfaceId}"`;const a=n.types.slice(0,3).join(", "),o=n.title?`: "${n.title}"`:"";return`"${t.surfaceId}" (${a}${o})`}).join("; ")}.`;return o>0&&(i+=` ...and ${o} more.`),i+=" To modify existing content, reuse its id in render_ui.",i.length>500&&(i=i.slice(0,497)+"..."),i}
@@ -1 +1 @@
1
- import{CloudHttpError}from"@beeos-ai/cloud-harness-sdk";import{resolveSyncAgentName}from"../agent-sync.js";import{AgentProjectionWorker,RuntimeProjectionTransportError}from"./agent-projection-worker.js";export class OpenClawAgentProjectionSource{getGateway;runtimeIds=new Map;constructor(t){this.getGateway=t}async listAgents(){const t=this.getGateway();if(!t?.isConnected)return{complete:!1,agents:[]};const e=await t.request("agents.list",{});return Array.isArray(e?.agents)?(this.runtimeIds.clear(),{complete:!0,agents:e.agents.map(t=>{const e=resolveSyncAgentName(t);return this.runtimeIds.set(e,t.id),{localKey:e,displayName:t.identity?.name||t.name||e,...t.description?{description:t.description}:{},...t.version?{version:t.version}:{},...t.identity?.avatarUrl?{avatarUrl:t.identity.avatarUrl}:{},...t.homepage?{homepage:t.homepage}:{},...t.defaultModel?{defaultModel:t.defaultModel}:{}}}).filter(t=>t.localKey)}):{complete:!1,agents:[]}}async listSkills(t){const e=this.getGateway();if(!e?.isConnected)return{complete:!1,skills:[]};try{const i=await e.request("skills.status",{agentId:this.runtimeIds.get(t)??t});return Array.isArray(i?.skills)?{complete:!0,skills:i.skills.filter(t=>!1!==t.eligible).map(t=>({skillKey:t.skillKey||t.name,name:t.name,...t.description?{description:t.description}:{},enabled:!t.disabled,...t.version?{version:t.version}:{}}))}:{complete:!1,skills:[]}}catch{return{complete:!1,skills:[]}}}}export class AgentGatewayHttpProjectionTransport{options;constructor(t){this.options=t}async apply(t,e){const i={runtimeEpoch:t.runtimeEpoch,revision:t.revision,snapshotId:t.snapshotId,canonicalHash:t.canonicalHash,agentsComplete:t.agentsComplete,skillsComplete:t.agents.every(t=>t.skillsComplete),agents:t.agents.map(t=>({runtimeAgentUid:t.runtimeAgentUid,localKey:t.localKey,displayName:t.displayName,...t.description?{description:t.description}:{},...t.version?{version:t.version}:{},...t.avatarUrl?{avatarUrl:t.avatarUrl}:{},...t.homepage?{homepage:t.homepage}:{},...t.defaultModel?{defaultModel:t.defaultModel}:{},capabilities:t.capabilities,skills:t.skills.map(t=>({skillKey:t.skillKey,name:t.name,...t.description?{description:t.description}:{},enabled:t.enabled,...t.version?{version:t.version}:{}})),skillsComplete:t.skillsComplete})),runtimeLeaseCredential:this.options.getRuntimeLeaseCredential(t)};let n;try{n=await this.options.gateway.agents.sync(i,{signal:e})}catch(t){if(!(t instanceof CloudHttpError))throw t;const e=t.transient,i=t.body.replace(/"runtimeLeaseCredential"\s*:\s*"[^"]*"/g,'"runtimeLeaseCredential":"<redacted>"').slice(0,512);throw new RuntimeProjectionTransportError(`Agent Gateway projection HTTP ${t.status}${i?`: ${i}`:""}`,e)}const r=n.acceptanceStatus,o=stringInteger(n.runtimeEpoch),a=stringInteger(n.revision);if(!o||!a||!["accepted","idempotent","stale_epoch","stale_revision","revision_hash_conflict"].includes(String(r)))throw new RuntimeProjectionTransportError("Agent Gateway returned malformed projection authority",!1);if("accepted"===r||"idempotent"===r){if(n.snapshotId!==t.snapshotId||n.canonicalHash!==t.canonicalHash||o!==t.runtimeEpoch||a!==t.revision)throw new RuntimeProjectionTransportError("Agent Gateway projection response correlation mismatch",!1);const e=validateAcceptedRows(n.data,t),i=e.map(t=>({runtimeAgentUid:t.runtimeAgentUid,platformAgentId:t.platformAgentId})),s=new Map;for(const t of e)s.set(t.runtimeLocalKey,{id:t.platformAgentId,instanceId:t.instanceId,name:t.runtimeLocalKey,displayName:String(t.raw.displayName??t.runtimeLocalKey),description:String(t.raw.description??""),version:String(t.raw.version??""),avatarUrl:String(t.raw.avatarUrl??""),status:String(t.raw.status??"active")});return this.options.onAccepted?.(s),{status:r,acceptedRuntimeEpoch:o,acceptedRevision:a,canonicalHash:String(n.canonicalHash),bindings:i}}return"revision_hash_conflict"===r?{status:r,acceptedRuntimeEpoch:o,acceptedRevision:a,acceptedCanonicalHash:String(n.canonicalHash??"")}:{status:r,acceptedRuntimeEpoch:o,acceptedRevision:a}}}const TEMPLATE_ORIGIN_REPORT_RETRY_LIMIT=3,TEMPLATE_ORIGIN_REPORT_RETRY_DELAY_MS=500;export class AgentGatewayTemplateOriginReporter{options;constructor(t){this.options=t}async report({platformAgentId:t,templateId:e}){let i;for(let n=1;n<=3;n++)try{return void await this.reportOnce(t,e)}catch(t){i=t,n<3&&await delay(this.options.retryDelayMs??500)}throw i instanceof Error?i:new Error(String(i))}async reportOnce(t,e){try{await this.options.gateway.agents.reportTemplateOrigin(t,e)}catch(t){if(!(t instanceof CloudHttpError))throw t;throw new Error(`Agent Gateway template-origin HTTP ${t.status}${t.body?`: ${t.body.slice(0,512)}`:""}`)}}}function delay(t){return new Promise(e=>setTimeout(e,t))}const TERMINAL_COMPACTION_RETRY_LIMIT=3,TERMINAL_COMPACTION_RETRY_DELAY_MS=250,TERMINAL_COMPACTION_BATCH_SIZE=32;export class AgentProjectionProduction{worker;unsubscribe;binding;transition=Promise.resolve();started=!1;terminalCompactionTimer;terminalCompactionRecovery;terminalCompactionAttempts=new Map;authority;journal;logger;constructor(t){this.authority=t.authority,this.journal=t.journal,this.logger=t.logger,this.worker=new AgentProjectionWorker({stateFile:t.stateFile,getLease:()=>t.authority.authorityLease,identities:t.identities,source:t.source,transport:t.transport,capabilities:t.capabilities,reconcileIntervalMs:t.reconcileIntervalMs,random:t.random,onBlocked:e=>t.logger?.warn?.(`[agent-projection] blocked: ${e}`),onReconciled:()=>this.onWorkerReconciled()})}async prepareCommittedMutation(t){return await this.worker.prepareCommittedMutation(t)}async applyCommittedMutation(t){return await this.worker.applyCommittedMutation(t)}async compactCommittedMutation(t){try{await this.worker.compactCommittedMutation(t),this.terminalCompactionAttempts.delete(t.operationId)}catch(t){throw this.scheduleTerminalCompactionRetry(),t}}start(){this.started||(this.started=!0,this.unsubscribe=this.authority.subscribeAuthority(t=>this.onAuthority(t)),this.onAuthority(this.authority.authorityLease),this.retryTerminalCompactions())}markDirty(){this.worker.markDirty()}onGatewayReconnect(){this.started&&this.authority.authorityLease&&(this.worker.reconcile("reconnect"),this.retryTerminalCompactions())}async stop(){this.started=!1,this.unsubscribe?.(),this.unsubscribe=void 0,this.terminalCompactionTimer&&clearTimeout(this.terminalCompactionTimer),this.terminalCompactionTimer=void 0,await this.queue(async()=>{this.binding=void 0,await this.worker.shutdown()})}onAuthority(t){const e=t?`${t.instanceId}\0${t.handlerIdentity}\0${t.runtimeEpoch}\0${t.leaseId}`:void 0;e!==this.binding&&(this.binding=e,this.queue(async()=>{await this.worker.shutdown(),this.started&&e&&e===this.binding&&this.worker.start()}))}queue(t){const e=this.transition.then(t,t);return this.transition=e.catch(()=>{}),e}onWorkerReconciled(){this.terminalCompactionAttempts.clear(),this.retryTerminalCompactions()}scheduleTerminalCompactionRetry(){this.started&&this.journal&&!this.terminalCompactionTimer&&(this.terminalCompactionTimer=setTimeout(()=>{this.terminalCompactionTimer=void 0,this.retryTerminalCompactions()},250))}async retryTerminalCompactions(){if(!this.started||!this.journal)return;if(this.terminalCompactionRecovery)return await this.terminalCompactionRecovery;const t=(async()=>{let t=!1;for(const e of(await this.worker.operationReferences()).slice(0,32)){const i=terminalIntentForReference(await this.journal.get(e.operationId),e);if(!i)continue;const n=this.terminalCompactionAttempts.get(e.operationId)??0;if(!(n>=3))try{await this.worker.compactCommittedMutation(i),this.terminalCompactionAttempts.delete(e.operationId)}catch(i){const r=n+1;this.terminalCompactionAttempts.set(e.operationId,r),this.logger?.warn?.(`[agent-projection] terminal reference compaction deferred (${r}/3): ${i instanceof Error?i.message:String(i)}`),t||=r<3}}t&&this.scheduleTerminalCompactionRetry()})();this.terminalCompactionRecovery=t;try{await t}finally{this.terminalCompactionRecovery===t&&(this.terminalCompactionRecovery=void 0)}}}function terminalIntentForReference(t,e){const i=t?.agentProjectionIntent;if(!i||i.operationId!==e.operationId||i.runtimeEpoch!==e.runtimeEpoch||i.projectionRevision!==e.projectionRevision||i.canonicalHash!==e.canonicalHash||i.snapshotRef!==e.snapshotRef)return;const n="projection_committed"===t.phase&&t.projectionReceipt?.revision===i.projectionRevision&&t.projectionReceipt.snapshot.revision===i.projectionRevision,r="blocked"===t.agentProjectionTerminal?.state&&t.agentProjectionTerminal.revision===i.projectionRevision;return n||r?i:void 0}function stringInteger(t){return"string"==typeof t&&/^(0|[1-9][0-9]*)$/.test(t)?t:Number.isSafeInteger(t)&&t>=0?String(t):null}const nonEmpty=t=>"string"==typeof t&&t.length>0?t:null;function validateAcceptedRows(t,e){if(!Array.isArray(t)||t.length!==e.agents.length)throw new RuntimeProjectionTransportError("Agent Gateway projection row count mismatch",!1);const i=new Map(e.agents.map(t=>[t.runtimeAgentUid,t.localKey])),n=new Set,r=new Set,o=new Set,a=t.map(t=>{if(!t||"object"!=typeof t||Array.isArray(t))throw new RuntimeProjectionTransportError("Agent Gateway projection row is malformed",!1);const a=t,s=nonEmpty(a.runtimeAgentUid),c=nonEmpty(a.runtimeLocalKey),l=nonEmpty(a.id),p=nonEmpty(a.instanceId);if(!s||!c||!l||!p||p!==e.instanceId||i.get(s)!==c||n.has(s)||r.has(c)||o.has(l))throw new RuntimeProjectionTransportError("Agent Gateway projection identity binding mismatch",!1);return n.add(s),r.add(c),o.add(l),{raw:a,runtimeAgentUid:s,runtimeLocalKey:c,platformAgentId:l,instanceId:p}});if(n.size!==i.size)throw new RuntimeProjectionTransportError("Agent Gateway projection omitted identity binding",!1);return a}
1
+ import{CloudHttpError}from"@beeos-ai/cloud-harness-sdk";import{resolveSyncAgentName}from"../agent-sync.js";import{AgentProjectionWorker,RuntimeProjectionTransportError}from"./agent-projection-worker.js";import{createSessionModelRefs}from"./openclaw-session-model.js";export class OpenClawAgentProjectionSource{getGateway;runtimeIds=new Map;constructor(t){this.getGateway=t}async listAgents(){const t=this.getGateway();if(!t?.isConnected)return{complete:!1,agents:[]};const e=await t.request("agents.list",{});if(!Array.isArray(e?.agents))return{complete:!1,agents:[]};this.runtimeIds.clear();const i=createSessionModelRefs();return{complete:!0,agents:e.agents.map(t=>{const e=resolveSyncAgentName(t),n=t.model?.primary?.trim();return this.runtimeIds.set(e,t.id),{localKey:e,displayName:t.identity?.name||t.name||e,...t.description?{description:t.description}:{},...t.version?{version:t.version}:{},...t.identity?.avatarUrl?{avatarUrl:t.identity.avatarUrl}:{},...t.homepage?{homepage:t.homepage}:{},...n?{defaultModel:i.toPlatform(n)}:{}}}).filter(t=>t.localKey)}}async listSkills(t){const e=this.getGateway();if(!e?.isConnected)return{complete:!1,skills:[]};try{const i=await e.request("skills.status",{agentId:this.runtimeIds.get(t)??t});return Array.isArray(i?.skills)?{complete:!0,skills:i.skills.filter(t=>!1!==t.eligible).map(t=>({skillKey:t.skillKey||t.name,name:t.name,...t.description?{description:t.description}:{},enabled:!t.disabled,...t.version?{version:t.version}:{}}))}:{complete:!1,skills:[]}}catch{return{complete:!1,skills:[]}}}}export class AgentGatewayHttpProjectionTransport{options;constructor(t){this.options=t}async apply(t,e){const i={runtimeEpoch:t.runtimeEpoch,revision:t.revision,snapshotId:t.snapshotId,canonicalHash:t.canonicalHash,agentsComplete:t.agentsComplete,skillsComplete:t.agents.every(t=>t.skillsComplete),agents:t.agents.map(t=>({runtimeAgentUid:t.runtimeAgentUid,localKey:t.localKey,displayName:t.displayName,...t.description?{description:t.description}:{},...t.version?{version:t.version}:{},...t.avatarUrl?{avatarUrl:t.avatarUrl}:{},...t.homepage?{homepage:t.homepage}:{},...t.defaultModel?{defaultModel:t.defaultModel}:{},capabilities:t.capabilities,skills:t.skills.map(t=>({skillKey:t.skillKey,name:t.name,...t.description?{description:t.description}:{},enabled:t.enabled,...t.version?{version:t.version}:{}})),skillsComplete:t.skillsComplete})),runtimeLeaseCredential:this.options.getRuntimeLeaseCredential(t)};let n;try{n=await this.options.gateway.agents.sync(i,{signal:e})}catch(t){if(!(t instanceof CloudHttpError))throw t;const e=t.transient,i=t.body.replace(/"runtimeLeaseCredential"\s*:\s*"[^"]*"/g,'"runtimeLeaseCredential":"<redacted>"').slice(0,512);throw new RuntimeProjectionTransportError(`Agent Gateway projection HTTP ${t.status}${i?`: ${i}`:""}`,e)}const r=n.acceptanceStatus,o=stringInteger(n.runtimeEpoch),a=stringInteger(n.revision);if(!o||!a||!["accepted","idempotent","stale_epoch","stale_revision","revision_hash_conflict"].includes(String(r)))throw new RuntimeProjectionTransportError("Agent Gateway returned malformed projection authority",!1);if("accepted"===r||"idempotent"===r){if(n.snapshotId!==t.snapshotId||n.canonicalHash!==t.canonicalHash||o!==t.runtimeEpoch||a!==t.revision)throw new RuntimeProjectionTransportError("Agent Gateway projection response correlation mismatch",!1);const e=validateAcceptedRows(n.data,t),i=e.map(t=>({runtimeAgentUid:t.runtimeAgentUid,platformAgentId:t.platformAgentId})),s=new Map;for(const t of e)s.set(t.runtimeLocalKey,{id:t.platformAgentId,instanceId:t.instanceId,name:t.runtimeLocalKey,displayName:String(t.raw.displayName??t.runtimeLocalKey),description:String(t.raw.description??""),version:String(t.raw.version??""),avatarUrl:String(t.raw.avatarUrl??""),status:String(t.raw.status??"active")});return this.options.onAccepted?.(s),{status:r,acceptedRuntimeEpoch:o,acceptedRevision:a,canonicalHash:String(n.canonicalHash),bindings:i}}return"revision_hash_conflict"===r?{status:r,acceptedRuntimeEpoch:o,acceptedRevision:a,acceptedCanonicalHash:String(n.canonicalHash??"")}:{status:r,acceptedRuntimeEpoch:o,acceptedRevision:a}}}const TEMPLATE_ORIGIN_REPORT_RETRY_LIMIT=3,TEMPLATE_ORIGIN_REPORT_RETRY_DELAY_MS=500;export class AgentGatewayTemplateOriginReporter{options;constructor(t){this.options=t}async report({platformAgentId:t,templateId:e}){let i;for(let n=1;n<=3;n++)try{return void await this.reportOnce(t,e)}catch(t){i=t,n<3&&await delay(this.options.retryDelayMs??500)}throw i instanceof Error?i:new Error(String(i))}async reportOnce(t,e){try{await this.options.gateway.agents.reportTemplateOrigin(t,e)}catch(t){if(!(t instanceof CloudHttpError))throw t;throw new Error(`Agent Gateway template-origin HTTP ${t.status}${t.body?`: ${t.body.slice(0,512)}`:""}`)}}}function delay(t){return new Promise(e=>setTimeout(e,t))}const TERMINAL_COMPACTION_RETRY_LIMIT=3,TERMINAL_COMPACTION_RETRY_DELAY_MS=250,TERMINAL_COMPACTION_BATCH_SIZE=32;export class AgentProjectionProduction{worker;unsubscribe;binding;transition=Promise.resolve();started=!1;terminalCompactionTimer;terminalCompactionRecovery;terminalCompactionAttempts=new Map;authority;journal;logger;constructor(t){this.authority=t.authority,this.journal=t.journal,this.logger=t.logger,this.worker=new AgentProjectionWorker({stateFile:t.stateFile,getLease:()=>t.authority.authorityLease,identities:t.identities,source:t.source,transport:t.transport,capabilities:t.capabilities,reconcileIntervalMs:t.reconcileIntervalMs,random:t.random,onBlocked:e=>t.logger?.warn?.(`[agent-projection] blocked: ${e}`),onReconciled:()=>this.onWorkerReconciled()})}async prepareCommittedMutation(t){return await this.worker.prepareCommittedMutation(t)}async applyCommittedMutation(t){return await this.worker.applyCommittedMutation(t)}async compactCommittedMutation(t){try{await this.worker.compactCommittedMutation(t),this.terminalCompactionAttempts.delete(t.operationId)}catch(t){throw this.scheduleTerminalCompactionRetry(),t}}start(){this.started||(this.started=!0,this.unsubscribe=this.authority.subscribeAuthority(t=>this.onAuthority(t)),this.onAuthority(this.authority.authorityLease),this.retryTerminalCompactions())}markDirty(){this.worker.markDirty()}onGatewayReconnect(){this.started&&this.authority.authorityLease&&(this.worker.reconcile("reconnect"),this.retryTerminalCompactions())}async stop(){this.started=!1,this.unsubscribe?.(),this.unsubscribe=void 0,this.terminalCompactionTimer&&clearTimeout(this.terminalCompactionTimer),this.terminalCompactionTimer=void 0,await this.queue(async()=>{this.binding=void 0,await this.worker.shutdown()})}onAuthority(t){const e=t?`${t.instanceId}\0${t.handlerIdentity}\0${t.runtimeEpoch}\0${t.leaseId}`:void 0;e!==this.binding&&(this.binding=e,this.queue(async()=>{await this.worker.shutdown(),this.started&&e&&e===this.binding&&this.worker.start()}))}queue(t){const e=this.transition.then(t,t);return this.transition=e.catch(()=>{}),e}onWorkerReconciled(){this.terminalCompactionAttempts.clear(),this.retryTerminalCompactions()}scheduleTerminalCompactionRetry(){this.started&&this.journal&&!this.terminalCompactionTimer&&(this.terminalCompactionTimer=setTimeout(()=>{this.terminalCompactionTimer=void 0,this.retryTerminalCompactions()},250))}async retryTerminalCompactions(){if(!this.started||!this.journal)return;if(this.terminalCompactionRecovery)return await this.terminalCompactionRecovery;const t=(async()=>{let t=!1;for(const e of(await this.worker.operationReferences()).slice(0,32)){const i=terminalIntentForReference(await this.journal.get(e.operationId),e);if(!i)continue;const n=this.terminalCompactionAttempts.get(e.operationId)??0;if(!(n>=3))try{await this.worker.compactCommittedMutation(i),this.terminalCompactionAttempts.delete(e.operationId)}catch(i){const r=n+1;this.terminalCompactionAttempts.set(e.operationId,r),this.logger?.warn?.(`[agent-projection] terminal reference compaction deferred (${r}/3): ${i instanceof Error?i.message:String(i)}`),t||=r<3}}t&&this.scheduleTerminalCompactionRetry()})();this.terminalCompactionRecovery=t;try{await t}finally{this.terminalCompactionRecovery===t&&(this.terminalCompactionRecovery=void 0)}}}function terminalIntentForReference(t,e){const i=t?.agentProjectionIntent;if(!i||i.operationId!==e.operationId||i.runtimeEpoch!==e.runtimeEpoch||i.projectionRevision!==e.projectionRevision||i.canonicalHash!==e.canonicalHash||i.snapshotRef!==e.snapshotRef)return;const n="projection_committed"===t.phase&&t.projectionReceipt?.revision===i.projectionRevision&&t.projectionReceipt.snapshot.revision===i.projectionRevision,r="blocked"===t.agentProjectionTerminal?.state&&t.agentProjectionTerminal.revision===i.projectionRevision;return n||r?i:void 0}function stringInteger(t){return"string"==typeof t&&/^(0|[1-9][0-9]*)$/.test(t)?t:Number.isSafeInteger(t)&&t>=0?String(t):null}const nonEmpty=t=>"string"==typeof t&&t.length>0?t:null;function validateAcceptedRows(t,e){if(!Array.isArray(t)||t.length!==e.agents.length)throw new RuntimeProjectionTransportError("Agent Gateway projection row count mismatch",!1);const i=new Map(e.agents.map(t=>[t.runtimeAgentUid,t.localKey])),n=new Set,r=new Set,o=new Set,a=t.map(t=>{if(!t||"object"!=typeof t||Array.isArray(t))throw new RuntimeProjectionTransportError("Agent Gateway projection row is malformed",!1);const a=t,s=nonEmpty(a.runtimeAgentUid),c=nonEmpty(a.runtimeLocalKey),l=nonEmpty(a.id),m=nonEmpty(a.instanceId);if(!s||!c||!l||!m||m!==e.instanceId||i.get(s)!==c||n.has(s)||r.has(c)||o.has(l))throw new RuntimeProjectionTransportError("Agent Gateway projection identity binding mismatch",!1);return n.add(s),r.add(c),o.add(l),{raw:a,runtimeAgentUid:s,runtimeLocalKey:c,platformAgentId:l,instanceId:m}});if(n.size!==i.size)throw new RuntimeProjectionTransportError("Agent Gateway projection omitted identity binding",!1);return a}
@@ -1 +1 @@
1
- const AROUTER_PROVIDER_ID="arouter";export const DEEPSEEK_REASONING_MODEL_ID="deepseek/deepseek-v4-flash";export const DEEPSEEK_REASONING_MODEL_KEY=`arouter/${DEEPSEEK_REASONING_MODEL_ID}`;function isRecord(e){return Boolean(e)&&"object"==typeof e&&!Array.isArray(e)}export function enableDeepseekReasoningStream(e){if(!e)return;const r=isRecord(e.models)?e.models:void 0,o=isRecord(r?.providers)?r.providers:void 0,s=isRecord(o?.arouter)?o.arouter:void 0,E=Array.isArray(s?.models)?s.models:void 0;if(!E)return e;let t=!1;const i=E.map(e=>isRecord(e)&&e.id===DEEPSEEK_REASONING_MODEL_ID?(t=!0,{...e,reasoning:!0}):e);if(!t)return e;const n=isRecord(e.agents)?e.agents:{},a=isRecord(n.defaults)?n.defaults:{},d=isRecord(a.models)?a.models:{},R=isRecord(d[DEEPSEEK_REASONING_MODEL_KEY])?d[DEEPSEEK_REASONING_MODEL_KEY]:{},D=isRecord(R.params)?R.params:{},_="string"==typeof D.thinking,c="string"==typeof a.reasoningDefault;return{...e,agents:{...n,defaults:{...a,reasoningDefault:c?a.reasoningDefault:"stream",models:{...d,[DEEPSEEK_REASONING_MODEL_KEY]:{...R,params:{...D,..._?{}:{thinking:"high"}}}}}},models:{...r,providers:{...o,[AROUTER_PROVIDER_ID]:{...s,models:i}}}}}
1
+ const AROUTER_PROVIDER_ID="arouter";export const DEEPSEEK_REASONING_MODEL_ID="deepseek/deepseek-v4-flash";export const DEEPSEEK_REASONING_MODEL_KEY=`arouter/${DEEPSEEK_REASONING_MODEL_ID}`;function isRecord(e){return Boolean(e)&&"object"==typeof e&&!Array.isArray(e)}export function enableDeepseekReasoningStream(e){if(!e)return;const r=isRecord(e.models)?e.models:void 0,o=isRecord(r?.providers)?r.providers:void 0,s=isRecord(o?.arouter)?o.arouter:void 0,t=Array.isArray(s?.models)?s.models:void 0;if(!t)return e;let n=!1;const E=t.map(e=>isRecord(e)&&e.id===DEEPSEEK_REASONING_MODEL_ID?(n=!0,{...e,reasoning:!0}):e);if(!n)return e;const a=isRecord(e.agents)?e.agents:{},i=isRecord(a.defaults)?a.defaults:{},d="string"==typeof i.reasoningDefault;return{...e,agents:{...a,defaults:{...i,reasoningDefault:d?i.reasoningDefault:"stream"}},models:{...r,providers:{...o,[AROUTER_PROVIDER_ID]:{...s,models:E}}}}}
@@ -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,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{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}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.6.63-cloud.12",
3
+ "version": "0.6.63-cloud.14",
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": "84f7023dba9535ccd5fc1b9a0ee28be3cfa60530"
91
+ "gitHead": "ff6b174621cb442dccf02933e367eaa4dfd07d8b"
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`);