beeos-claw 0.6.15 → 0.6.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-gateway/gateway-events.js +1 -1
- package/dist/acp-server.js +1 -1
- package/dist/config.js +1 -1
- package/dist/im-runtime-dispatch.js +1 -1
- package/dist/inbound-execution-journal.js +1 -0
- package/dist/index.js +1 -1
- package/dist/message-client-composition.js +1 -0
- package/dist/message-client-types.js +1 -0
- package/dist/message-projection-inbound-consumer.js +1 -0
- package/dist/runtime/conversation-title.js +1 -1
- package/dist/runtime/im-reply-sink.js +1 -1
- package/dist/runtime/message-service-runtime-message-channel.js +1 -1
- package/dist/runtime/ms-runtime-message-channel-adapter.js +1 -1
- package/dist/runtime/ms-runtime-message-channel-consumer-factory.js +1 -1
- package/dist/runtime/runtime-authority-message-bus-lifecycle.js +1 -0
- package/dist/runtime/runtime-service-production-candidate.js +1 -1
- package/openclaw.plugin.json +1 -5
- package/package.json +2 -2
- package/dist/im-recovery-scanner.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash,randomUUID}from"node:crypto";import{Errors}from"../errors/index.js";import{BEEOS_CHANNEL_TAG_RE,decodeBeeosChannelId,encodeBeeosChannelTag}from"./bridge-core.js";import{classifyAgentRuntimeFailure}from"../runtime/agent-runtime-failure-classifier.js";import{stripTransportMetadata,normalizeTransportText,previewSanitizedText}from"../message-sanitization/transport-metadata-sanitizer.js";import{classifyHeartbeatFilter}from"../message-sanitization/message-classifier.js";import{emitNormalizedToolResultForTurn}from"./tool-result-emitter.js";import{buildToolCallPayload}from"./tool-call-payload-builder.js";import{DEFAULT_STREAM_IDLE_TIMEOUT_MS,DEFAULT_STREAM_STALE_TIMEOUT_MS}from"./types.js";import{MessageQueue}from"../utils/mq.js";import{observeLifecyclePromise}from"../runtime/lifecycle-promise-observer.js";export function lifecycleEndFailureMessage(e){if(!e)return;const t=!0===e.isError||!0===e.is_error,s=e.error,o=s&&"object"==typeof s?s:void 0,n="string"==typeof s||void 0!==o||"string"==typeof e.message||"string"==typeof e.reason;if(t||n){if(!t){const t="string"==typeof e.stopReason?e.stopReason:"";if("error"!==t&&"timeout"!==t&&"aborted"!==t)return}return("string"==typeof e.message?e.message:void 0)??("string"==typeof s?s:void 0)??("string"==typeof o?.message?o.message:void 0)??("string"==typeof e.reason?e.reason:void 0)??(o?JSON.stringify(o):void 0)??"gateway lifecycle error"}}function asString(e){if("string"==typeof e){return e.trim()||void 0}}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function normalizeText(e){if("string"==typeof e)return normalizeTransportText(e.replace(/\r\n/g,"\n").replace(/\r/g,"\n"))}const DEDUP_HASH_THRESHOLD=256;function dedupKey(e,...t){const s=`${e}:${t.join(":")}`;if(s.length<=256)return s;return`${e}:h:${createHash("sha256").update(s).digest("hex").slice(0,24)}`}function toISO(e){if(!(null==e||e<=0))try{return new Date(e).toISOString()}catch{return}}function resolveToolArgs(e){const t=e.arguments??e.args??e.input;if(isRecord(t))return t;if("string"==typeof t){const e=t.trim();if(e.startsWith("{")||e.startsWith("["))try{return JSON.parse(e)}catch{}}return{}}export function resolveTurnId(e){if(e.currentRunId)return e.currentRunId;const t=e.pendingPromptId;return void 0!==t?"string"==typeof t?`req:${t}`:`req:${String(t)}`:void 0}export function roleFromSessionUpdate(e){const t=e.sessionUpdate;if(t)return"user_message_chunk"===t?"user":"system_message_chunk"===t?"system":t.startsWith("agent_")||t.startsWith("tool_")?"assistant":"history_turn"===t?"user"===e.role?"user":"system"===e.role?"system":"assistant":void 0}export function recordTurnIdForRole(e,t,s){if(!s)return;const o=roleFromSessionUpdate(t);o&&(e.lastTurnIdByRole||(e.lastTurnIdByRole={}),e.lastTurnIdByRole[o]=s)}export function recordToolCallTurnForUpdate(e,t,s){s&&"tool_call"===t.sessionUpdate&&t.toolCallId&&(e.toolCallTurns??=new Map,e.toolCallTurns.set(t.toolCallId,{turnId:s,..."string"==typeof t.name&&t.name?{toolName:t.name}:{}}))}export class AcpGatewayEvents{state;transport;config;logger;writeObs;completePrompt;failPrompt;interruptPrompt;schedulePromptTimeout;adoptBackgroundMutation;abortGatewayRun;maybeCompletePromptFromChatState;agentFetchFn;getMessageClient;getMsgPrincipalId;gatewayRequest;getCanvasState;cronChannelCache;constructor(e){this.state=e.state,this.transport=e.transport,this.config=e.config,this.logger=e.logger,this.writeObs=e.writeObs,this.completePrompt=e.completePrompt,this.failPrompt=e.failPrompt,this.interruptPrompt=e.interruptPrompt,this.schedulePromptTimeout=e.schedulePromptTimeout,this.adoptBackgroundMutation=e.adoptBackgroundMutation,this.abortGatewayRun=e.abortGatewayRun,this.maybeCompletePromptFromChatState=e.maybeCompletePromptFromChatState,this.agentFetchFn=e.agentFetch,this.getMessageClient=e.getMessageClient,this.getMsgPrincipalId=e.getMsgPrincipalId,this.gatewayRequest=e.gatewayRequest,this.getCanvasState=e.getCanvasState,this.cronChannelCache=e.cronChannelCache}observeLifecycle(e,t,s){observeLifecyclePromise(e,this.logger,{component:"gateway-events",action:t,sessionId:s.sessionId,gatewaySessionKey:s.gatewaySessionKey})}touchStreamActivity(e){e.lastStreamActivityTs=Date.now(),this.resetStreamIdleTimer(e)}resetStreamIdleTimer(e){this.state.clearStreamIdleTimer(e);const t=this.config.streamIdleTimeoutMs??DEFAULT_STREAM_IDLE_TIMEOUT_MS,s=this.config.streamStaleTimeoutMs??DEFAULT_STREAM_STALE_TIMEOUT_MS;e.streamIdleTimer=setTimeout(()=>{if(!e.lastStreamActivityTs)return;const t=Date.now()-e.lastStreamActivityTs;t>s&&(this.logger.warn?.(`[gateway-events] Stream stale timeout sessionId=${e.sessionId} stale=${t}ms`),this.observeLifecycle(this.interruptPrompt(e,classifyAgentRuntimeFailure({raw:`Stream stale timeout after ${t}ms`,source:"live_gateway",runtimeKind:"openclaw"})),"stream_stale_timeout",e))},t),"object"==typeof e.streamIdleTimer&&"unref"in e.streamIdleTimer&&e.streamIdleTimer.unref()}sendTurnUpdate(e,t){const s=resolveTurnId(e);return s&&void 0===t._turnId&&(t._turnId=s),recordTurnIdForRole(e,t,s),recordToolCallTurnForUpdate(e,t,s),!!e.sessionLoadInProgress||this.transport.sendSessionUpdate(e.sessionId,t,{turnId:s})}sendToolCallSpacer(e,t){const s={sessionUpdate:"agent_message_chunk",content:{type:"text",text:"\n"},...t?{timestamp:t}:{}};this.sendTurnUpdate(e,s),e.assistantMq?.push(s)}resolveToolCallId(e,t,s){const o=asString(t.toolCallId)??asString(t.tool_call_id)??asString(t.callId)??asString(t.id);if(o){const t=this.state.toolCallIdMap.get(o);return t?(e.sessionId&&this.state.toolCallToSessionMap.set(t,e.sessionId),t):(this.state.toolCallIdMap.set(o,o),e.sessionId&&this.state.toolCallToSessionMap.set(o,e.sessionId),o)}const n=`tc_${e.currentRunId??"unknown"}_${this.state.nextToolCallSeq++}`;return e.sessionId&&this.state.toolCallToSessionMap.set(n,e.sessionId),n}handleChatEvent(e){const t=(e.runId?this.state.promptsByRunId.get(e.runId):void 0)??this.state.findSessionByKey(e.sessionKey);if(!t)return;if(!this.state.isCurrentStreamOwner(t,e.runId)){const s=!t.currentRunId&&void 0===t.pendingPromptId;if(!e.runId||!s||this.state.isRunCompleted(e.runId))return;if(!this.adoptBackgroundRun(t,e.runId))return}t.sessionId;const s=e.message;if(s){this.touchStreamActivity(t);if("assistant"===s.role){if(!Array.isArray(s.content))return;for(const o of s.content){const n=o;if(!isRecord(n))continue;const a=asString(n.type);if(!a)continue;const i=toISO(s.timestamp);if("thinking"===a){if(t.hasAgentThinkingStream)continue;const e=normalizeText(n.thinking)??normalizeText(n.text);if(!e)continue;const s=dedupKey("chat:thinking",e);if(t.chatReplayDedupKeys.has(s))continue;t.chatReplayDedupKeys.add(s);const o={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:e},...i?{timestamp:i}:{}};this.sendTurnUpdate(t,o)&&t.assistantMq?.push(o);continue}if("toolCall"===a){if(this.sendToolCallSpacer(t,i),t.hasAgentToolStartStream)continue;const e=this.resolveToolCallId(t,n,"start"),s=asString(n.name)??asString(n.toolName)??asString(n.tool_name)??"tool",o=resolveToolArgs(n),a=dedupKey("chat:tool_start",e,JSON.stringify(o));if(t.chatReplayDedupKeys.has(a))continue;t.chatReplayDedupKeys.add(a);const r=buildToolCallPayload({toolCallId:e,toolName:s,args:o,timestamp:i});this.sendTurnUpdate(t,r)&&t.assistantMq?.push(r);continue}if("toolResult"===a){if(this.sendToolCallSpacer(t,i),t.hasAgentToolResultStream)continue;const e=this.resolveToolCallId(t,n,"result"),s=asString(n.name)??asString(n.toolName)??asString(n.tool_name)??"tool",o=n.result,a=normalizeText(n.text)??normalizeText(n.result)??(isRecord(o)||Array.isArray(o)?JSON.stringify(o,null,2):void 0);if(!a)continue;const r=dedupKey("chat:tool_result",e,a);if(t.chatReplayDedupKeys.has(r))continue;t.chatReplayDedupKeys.add(r),this.emitNormalizedToolResult(t,{toolCallId:e,toolName:s,rawText:a,rawResult:o,timestamp:i}).catch(e=>{this.logger.warn?.(`[gateway-events] Tool result normalization failed: ${e instanceof Error?e.message:e}`)});continue}if("text"===a&&n.text){if(t.hasAgentAssistantStream||t.agentAssistantTextSoFar)continue;const s=stripTransportMetadata(n.text);if(!s)continue;const o=classifyHeartbeatFilter(s);if(o.filtered){this.logHeartbeatSkip({source:"chat-text",reason:o.reason,sessionId:t.sessionId,runId:e.runId,cumulative:s});continue}const a=dedupKey("chat:text",s);if(t.chatReplayDedupKeys.has(a))continue;t.chatReplayDedupKeys.add(a);const r=t.chatAssistantTextSoFar??"";let l;if(t.chatAssistantTextSoFar=s,s.startsWith(r))l=s.slice(r.length);else{if(r.startsWith(s))continue;l=s}if(!l)continue;this.logger.debug?.(`[gateway-events] chat.text delta seq=${e.seq} runId=${e.runId} len=${l.length} prevLen=${r.length}`);const c={sessionUpdate:"agent_message_chunk",content:{type:"text",text:l},...i?{timestamp:i}:{}};this.sendTurnUpdate(t,c),t.assistantMq?.push(c)}}}}else"delta"===e.state&&this.touchStreamActivity(t);if(this.maybeCompletePromptFromChatState(t,e),!t.promptDone)if("final"===e.state)this.observeLifecycle(this.completePrompt(t,e.stopReason||"end_turn"),"chat_final",t);else if("aborted"===e.state||"cancelled"===e.state||"cancel"===e.state)this.observeLifecycle(this.completePrompt(t,"cancelled"),"chat_cancelled",t);else if("error"===e.state){const s=e.errorMessage||Errors.AgentError.message,o=classifyAgentRuntimeFailure({raw:s,source:"chat_state",runtimeKind:"openclaw"});"interrupted"===o.kind?this.observeLifecycle(this.interruptPrompt(t,o),"chat_interrupt",t):this.observeLifecycle(this.failPrompt(t,Errors.AgentError.rpcCode,o.publicMessage),"chat_failure",t)}}handleAgentEvent(e){const t=(e.runId?this.state.promptsByRunId.get(e.runId):void 0)??(e.sessionKey?this.state.findSessionByKey(e.sessionKey):void 0);if(!t)return;if(!this.state.isCurrentStreamOwner(t,e.runId)){const s=!t.currentRunId&&void 0===t.pendingPromptId;if(!e.runId||!s||this.state.isRunCompleted(e.runId))return;if(!this.adoptBackgroundRun(t,e.runId))return}e.runId&&t.cronJobId&&!this.state.runIdToCronJobId.has(e.runId)&&this.state.setRunCronJobId(e.runId,t.cronJobId);t.sessionId;this.touchStreamActivity(t);const s=toISO(e.ts),o=e=>{this.sendTurnUpdate(t,e),t.assistantMq?.push(e)};if("assistant"===e.stream){t.hasAgentAssistantStream=!0;const n=this.state.extractAgentAssistantDelta(t,e.data??{});if(n){const a=normalizeTransportText(n);if(a){const n=t.agentAssistantTextSoFar??a,i=classifyHeartbeatFilter(n);i.filtered?this.logHeartbeatSkip({source:"assistant-delta",reason:i.reason,sessionId:t.sessionId,runId:e.runId,cumulative:n,delta:a}):(this.logger.debug?.(`[gateway-events] agent.assistant delta seq=${e.seq} runId=${e.runId} len=${a.length} source=${e.data?.delta?"delta":"cumulative"}`),o({sessionUpdate:"agent_message_chunk",content:{type:"text",text:a},...s?{timestamp:s}:{}}))}}const a=[],i=e.data;i&&(Array.isArray(i.content)?a.push(...i.content):isRecord(i.content)?a.push(i.content):"string"==typeof i.type&&a.push(i));for(const e of a){if(!isRecord(e))continue;const n=asString(e.type);if("thinking"===n){t.hasAgentThinkingStream=!0;const o=normalizeText(e.thinking)??normalizeText(e.text);if(o){const e={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:o},...s?{timestamp:s}:{}};this.sendTurnUpdate(t,e)&&t.assistantMq?.push(e)}continue}if("toolCall"===n){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const n=this.resolveToolCallId(t,e,"start"),a=asString(e.name)??asString(e.toolName)??asString(e.tool_name)??"tool",i=resolveToolArgs(e);o(buildToolCallPayload({toolCallId:n,toolName:a,args:i,timestamp:s}));continue}if("toolResult"===n){this.sendToolCallSpacer(t,s),t.hasAgentToolResultStream=!0;const n=this.resolveToolCallId(t,e,"result"),a=asString(e.name)??asString(e.toolName)??asString(e.tool_name)??"tool",i=e.result,r=normalizeText(e.text)??normalizeText(e.result)??(isRecord(i)||Array.isArray(i)?JSON.stringify(i,null,2):void 0);r&&this.emitNormalizedToolResult(t,{toolCallId:n,toolName:a,rawText:r,rawResult:i,timestamp:s,pushUpdate:o}).catch(e=>{this.logger.warn?.(`[gateway-events] Tool result normalization failed: ${e instanceof Error?e.message:e}`)});continue}}}else if("thinking"===e.stream){t.hasAgentThinkingStream=!0;const o=normalizeText(e.data?.delta)??normalizeText(e.data?.text);if(o){const e={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:o},...s?{timestamp:s}:{}};this.sendTurnUpdate(t,e)&&t.assistantMq?.push(e)}}else if("tool"===e.stream){const n=e.data,a="string"==typeof n?.phase?n.phase:void 0;if("start"===a){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const e=this.resolveToolCallId(t,n,"start"),a=asString(n?.name)??asString(n?.toolName)??asString(n?.tool_name)??"tool",i=resolveToolArgs(n);o(buildToolCallPayload({toolCallId:e,toolName:a,args:i,timestamp:s}))}else if("result"===a)this.sendToolCallSpacer(t,s),t.hasAgentToolResultStream=!0,this.handleToolResult(t,e,s).catch(e=>{this.logger.warn?.(`[gateway-events] Tool result handling failed: ${e instanceof Error?e.message:e}`)});else{const e=n?.toolCall;if(e){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const a=asString(e?.name)??asString(n?.name)??asString(n?.toolName)??"tool",i={...n,...e},r=this.resolveToolCallId(t,i,"start"),l=resolveToolArgs(i);o(buildToolCallPayload({toolCallId:r,toolName:a,args:l,timestamp:s}))}}}else if("lifecycle"===e.stream){const s=e.data,o="string"==typeof s?.phase?s.phase:void 0,n=(o,n)=>{this.logger.warn?.(`[gateway-events] lifecycle error runId=${e.runId} sessionKey=${e.sessionKey} message=${o} data=${JSON.stringify(s)}`);const a=classifyAgentRuntimeFailure({raw:o,source:"live_gateway",runtimeKind:"openclaw"});"interrupted"===a.kind?this.observeLifecycle(this.interruptPrompt(t,a),"agent_interrupt",t):this.observeLifecycle(this.failPrompt(t,Errors.AgentError.rpcCode,a.publicMessage),n,t)};if("end"===o){const e=lifecycleEndFailureMessage(s);if(e)n(e,"agent_failure");else{const e="string"==typeof s?.stopReason?s.stopReason:void 0;this.observeLifecycle(this.completePrompt(t,e||"end_turn"),"agent_final",t)}}else if("cancelled"===o||"cancel"===o)this.observeLifecycle(this.completePrompt(t,"cancelled"),"agent_cancelled",t);else if("error"===o){const e=s?.error,t=e&&"object"==typeof e?e:void 0;n(("string"==typeof s?.message?s.message:void 0)??("string"==typeof e?e:void 0)??("string"==typeof t?.message?t.message:void 0)??("string"==typeof s?.reason?s.reason:void 0)??(t?JSON.stringify(t):void 0)??"gateway lifecycle error","agent_failure")}}}async handleToolResult(e,t,s){const o=t.data,n=o?.toolCall,a=("string"==typeof n?.id?n.id:void 0)??("string"==typeof o?.toolCallId?o.toolCallId:void 0),i=(a?this.state.toolCallIdMap.get(a):void 0)??a??`tc_${t.runId}_${t.seq}`,r=("string"==typeof n?.name?n.name:void 0)??("string"==typeof o?.name?o.name:void 0)??"tool",l="string"==typeof o?.text?o.text:void 0,c=a??i,d=o?.result;await this.emitNormalizedToolResult(e,{toolCallId:i,normalizeToolCallId:c,toolName:r,rawText:l,rawResult:d,timestamp:s})}async emitNormalizedToolResult(e,t){const s=t.pushUpdate??(t=>{this.sendTurnUpdate(e,t)&&e.assistantMq?.push(t)});await emitNormalizedToolResultForTurn({session:e,envelope:{toolCallId:t.toolCallId,toolName:t.toolName,rawText:t.rawText,rawResult:t.rawResult,timestamp:t.timestamp},normalizeToolCallId:t.normalizeToolCallId,turnId:resolveTurnId(e),config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:this.getCanvasState,sendUpdate:e=>(s(e),!0)})}adoptBackgroundRun(e,t){this.logger.debug?.(`[gateway-events] Adopting background run ${t} on session ${e.sessionId} (prev=${e.currentRunId})`);if(!this.adoptBackgroundMutation(e,t).ok)return this.observeLifecycle(this.abortGatewayRun(e.gatewaySessionKey,t),"background_run_conflict_abort",e),!1;e.currentRunId=t,e.promptDone=!1,this.state.promptsByRunId.set(t,e);const s=this.state.runIdToCronJobId.get(t);return s&&(e.cronJobId=s),e.assistantStreamStartedAt=Date.now(),e.lastStreamActivityTs=Date.now(),e.hasAgentAssistantStream=void 0,e.hasAgentThinkingStream=void 0,e.hasAgentToolStartStream=void 0,e.hasAgentToolResultStream=void 0,e.chatAssistantTextSoFar=void 0,e.agentAssistantTextSoFar=void 0,e.chatReplayDedupKeys?.clear(),e.presentedFileArtifactKeys=new Set,e.emittedToolResultKeys=new Set,e.assistantMq&&!e.assistantMq.closed||(e.assistantMq=new MessageQueue),this.schedulePromptTimeout(e),!0}handleCronEvent(e){this.transport.sendNotification("_beeos/cron_event",{payload:e});const t=e??{};"string"==typeof t.runId&&"string"==typeof t.jobId&&this.state.setRunCronJobId(t.runId,t.jobId),this.config.agentGatewayUrl&&this.agentFetchFn&&this.reportCronEventToAgentGateway(t),"added"===t.action&&this.maybeBackfillBeeosChannel(t),"finished"===t.action&&"string"==typeof t.summary&&t.summary&&this.publishCronResult(t)}async reportCronEventToAgentGateway(e){if(!this.config.agentGatewayUrl||!this.agentFetchFn)return;const t={...e},s="string"==typeof e.sessionKey?e.sessionKey:void 0;s&&null==t.sessionKey&&(t.sessionKey=s);const o=s?this.state.findSessionByKey(s)?.sessionId:void 0;if(o&&(t.sessionId=o),("string"!=typeof t.name||!t.name)&&"string"==typeof e.jobId&&e.jobId&&this.gatewayRequest)try{const s=await this.gatewayRequest("cron.list",{}),o=s?.jobs;if(Array.isArray(o))for(const s of o)if(isRecord(s)&&s.id===e.jobId&&"string"==typeof s.name&&s.name){t.name=s.name;break}}catch(e){this.logger.debug?.(`[gateway-events] cron.list lookup failed: ${e instanceof Error?e.message:e}`)}const n=`${this.config.agentGatewayUrl}/api/v1/agent/events`;try{await this.agentFetchFn(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"cron",data:t})})}catch(e){this.logger.warn?.(`[gateway-events] event report failed: ${e instanceof Error?e.message:e}`)}}async maybeBackfillBeeosChannel(e){const t="string"==typeof e.jobId?e.jobId:void 0;if(!t)return;if(!this.gatewayRequest)return;if(this.cronChannelCache?.get(t))return;let s;try{const e=await this.gatewayRequest("cron.list",{}),o=e?.jobs;if(Array.isArray(o))for(const e of o){if(!e||"object"!=typeof e)continue;const o=e;if(o.id===t){s=o;break}}}catch(e){return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel cron.list failed: ${e instanceof Error?e.message:e}`)}if(!s)return;const o="string"==typeof s.description?s.description:"";if(BEEOS_CHANNEL_TAG_RE.test(o)){const e=decodeBeeosChannelId(o);return void(e&&this.setCronChannelCacheLocal(t,e))}if("main"===("string"==typeof s.sessionTarget?s.sessionTarget:void 0))return;const n=isRecord(s.payload)?s.payload:void 0;if("systemEvent"===(n&&"string"==typeof n.kind?n.kind:void 0))return;const a=isRecord(s.delivery)?s.delivery:void 0;if(a&&"none"===a.mode)return;const i="string"==typeof s.sessionKey?s.sessionKey:void 0;if(!i)return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel: job has no sessionKey job=${t}`);const r=this.state.findSessionByKey(i),l=r?.channelId;if(!l)return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel: no channelId on ACPSession for sessionKey=${i} job=${t}`);const c=encodeBeeosChannelTag(o,l);try{await this.gatewayRequest("cron.update",{id:t,patch:{description:c}}),this.setCronChannelCacheLocal(t,l),this.logger.info?.(`[gateway-events] backfilled beeos:channel for LLM-created cron job=${t} channel=${l}`)}catch(e){this.logger.warn?.(`[gateway-events] maybeBackfillBeeosChannel cron.update failed: ${e instanceof Error?e.message:e}`)}}setCronChannelCacheLocal(e,t){this.cronChannelCache&&this.cronChannelCache.set(e,t)}async publishCronResult(e){const t=this.getMessageClient?.();if(!t)return;const s=this.getMsgPrincipalId?.();if(!s)return;const o="string"==typeof e.jobId?e.jobId:void 0;if(!o)return;const n=e.summary;let a=this.cronChannelCache?.get(o);if(!a&&this.gatewayRequest)try{const e=await this.gatewayRequest("cron.list",{}),t=e?.jobs;if(Array.isArray(t))for(const e of t){if(!e||"object"!=typeof e)continue;const t=e;if(t.id===o){a=decodeBeeosChannelId(t.description);break}}}catch(e){this.logger.debug?.(`[gateway-events] cron.list fallback failed: ${e instanceof Error?e.message:e}`)}if(!a)return;const i=randomUUID();try{await t.messages.send({conversationId:a,id:i,type:"agent_reply",content:{text:n,cron_job_id:o,session_key:e.sessionKey??void 0}}),this.logger.info?.(`[gateway-events] published cron result to channel=${a} job=${o}`)}catch(e){const t=e instanceof Error?e.message:String(e);if(isChannelGoneError(e,t))return this.logger.info?.(`[gateway-events] origin channel gone (session likely deleted), skipping publish: channel=${a} job=${o} err=${t}`),void this.cronChannelCache?.delete(o);this.logger.warn?.(`[gateway-events] publishCronResult failed: ${t}`)}}logHeartbeatSkip(e){const t=previewSanitizedText(e.cumulative),s=void 0!==e.delta?previewSanitizedText(e.delta):void 0;this.logger.info?.(`[gateway-events] SKIP heartbeat ${e.source} reason=${e.reason} sessionId=${e.sessionId} runId=${e.runId??"n/a"} cumLen=${e.cumulative.length}`+(void 0!==e.delta?` deltaLen=${e.delta.length}`:"")+` cumulative="${t}"`+(void 0!==s?` delta="${s}"`:"")),this.writeObs({component:"gateway-events",domain:"lifecycle",name:"heartbeat_filter_skip",severity:"info",summary:`heartbeat ${e.source} drop (${e.reason})`,payload:{source:e.source,reason:e.reason,sessionId:e.sessionId,runId:e.runId,cumulativeLen:e.cumulative.length,cumulativePreview:t,...void 0!==e.delta?{deltaLen:e.delta.length,deltaPreview:s}:{}}})}}export function isChannelGoneError(e,t){const s=t.toLowerCase();if(s.includes("not found")||s.includes("no such channel")||s.includes("channel does not exist")||s.includes("channel gone")||s.includes("410")||/\b404\b/.test(s))return!0;if(isRecord(e)){const t=e.status??e.statusCode??e.code;if(404===t||410===t)return!0}return!1}
|
|
1
|
+
import{createHash,randomUUID}from"node:crypto";import{Errors}from"../errors/index.js";import{BEEOS_CHANNEL_TAG_RE,decodeBeeosChannelId,encodeBeeosChannelTag}from"./bridge-core.js";import{classifyAgentRuntimeFailure}from"../runtime/agent-runtime-failure-classifier.js";import{stripTransportMetadata,normalizeTransportText,previewSanitizedText}from"../message-sanitization/transport-metadata-sanitizer.js";import{classifyHeartbeatFilter}from"../message-sanitization/message-classifier.js";import{emitNormalizedToolResultForTurn}from"./tool-result-emitter.js";import{buildToolCallPayload}from"./tool-call-payload-builder.js";import{DEFAULT_STREAM_IDLE_TIMEOUT_MS,DEFAULT_STREAM_STALE_TIMEOUT_MS}from"./types.js";import{MessageQueue}from"../utils/mq.js";import{observeLifecyclePromise}from"../runtime/lifecycle-promise-observer.js";export function lifecycleEndFailureMessage(e){if(!e)return;const t=!0===e.isError||!0===e.is_error,s=e.error,o=s&&"object"==typeof s?s:void 0,n="string"==typeof s||void 0!==o||"string"==typeof e.message||"string"==typeof e.reason;if(t||n){if(!t){const t="string"==typeof e.stopReason?e.stopReason:"";if("error"!==t&&"timeout"!==t&&"aborted"!==t)return}return("string"==typeof e.message?e.message:void 0)??("string"==typeof s?s:void 0)??("string"==typeof o?.message?o.message:void 0)??("string"==typeof e.reason?e.reason:void 0)??(o?JSON.stringify(o):void 0)??"gateway lifecycle error"}}function asString(e){if("string"==typeof e){return e.trim()||void 0}}function isRecord(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function normalizeText(e){if("string"==typeof e)return normalizeTransportText(e.replace(/\r\n/g,"\n").replace(/\r/g,"\n"))}const DEDUP_HASH_THRESHOLD=256;function dedupKey(e,...t){const s=`${e}:${t.join(":")}`;if(s.length<=256)return s;return`${e}:h:${createHash("sha256").update(s).digest("hex").slice(0,24)}`}function toISO(e){if(!(null==e||e<=0))try{return new Date(e).toISOString()}catch{return}}function resolveToolArgs(e){const t=e.arguments??e.args??e.input;if(isRecord(t))return t;if("string"==typeof t){const e=t.trim();if(e.startsWith("{")||e.startsWith("["))try{return JSON.parse(e)}catch{}}return{}}export function resolveTurnId(e){if(e.currentRunId)return e.currentRunId;const t=e.pendingPromptId;return void 0!==t?"string"==typeof t?`req:${t}`:`req:${String(t)}`:void 0}export function roleFromSessionUpdate(e){const t=e.sessionUpdate;if(t)return"user_message_chunk"===t?"user":"system_message_chunk"===t?"system":t.startsWith("agent_")||t.startsWith("tool_")?"assistant":"history_turn"===t?"user"===e.role?"user":"system"===e.role?"system":"assistant":void 0}export function recordTurnIdForRole(e,t,s){if(!s)return;const o=roleFromSessionUpdate(t);o&&(e.lastTurnIdByRole||(e.lastTurnIdByRole={}),e.lastTurnIdByRole[o]=s)}export function recordToolCallTurnForUpdate(e,t,s){s&&"tool_call"===t.sessionUpdate&&t.toolCallId&&(e.toolCallTurns??=new Map,e.toolCallTurns.set(t.toolCallId,{turnId:s,..."string"==typeof t.name&&t.name?{toolName:t.name}:{}}))}export class AcpGatewayEvents{state;transport;config;logger;writeObs;completePrompt;failPrompt;interruptPrompt;schedulePromptTimeout;adoptBackgroundMutation;abortGatewayRun;maybeCompletePromptFromChatState;agentFetchFn;getMessageClient;getMsgPrincipalId;gatewayRequest;getCanvasState;cronChannelCache;constructor(e){this.state=e.state,this.transport=e.transport,this.config=e.config,this.logger=e.logger,this.writeObs=e.writeObs,this.completePrompt=e.completePrompt,this.failPrompt=e.failPrompt,this.interruptPrompt=e.interruptPrompt,this.schedulePromptTimeout=e.schedulePromptTimeout,this.adoptBackgroundMutation=e.adoptBackgroundMutation,this.abortGatewayRun=e.abortGatewayRun,this.maybeCompletePromptFromChatState=e.maybeCompletePromptFromChatState,this.agentFetchFn=e.agentFetch,this.getMessageClient=e.getMessageClient,this.getMsgPrincipalId=e.getMsgPrincipalId,this.gatewayRequest=e.gatewayRequest,this.getCanvasState=e.getCanvasState,this.cronChannelCache=e.cronChannelCache}observeLifecycle(e,t,s){observeLifecyclePromise(e,this.logger,{component:"gateway-events",action:t,sessionId:s.sessionId,gatewaySessionKey:s.gatewaySessionKey})}touchStreamActivity(e){e.lastStreamActivityTs=Date.now(),this.resetStreamIdleTimer(e)}resetStreamIdleTimer(e){this.state.clearStreamIdleTimer(e);const t=this.config.streamIdleTimeoutMs??DEFAULT_STREAM_IDLE_TIMEOUT_MS,s=this.config.streamStaleTimeoutMs??DEFAULT_STREAM_STALE_TIMEOUT_MS;e.streamIdleTimer=setTimeout(()=>{if(!e.lastStreamActivityTs)return;const t=Date.now()-e.lastStreamActivityTs;t>s&&(this.logger.warn?.(`[gateway-events] Stream stale timeout sessionId=${e.sessionId} stale=${t}ms`),this.observeLifecycle(this.interruptPrompt(e,classifyAgentRuntimeFailure({raw:`Stream stale timeout after ${t}ms`,source:"live_gateway",runtimeKind:"openclaw"})),"stream_stale_timeout",e))},t),"object"==typeof e.streamIdleTimer&&"unref"in e.streamIdleTimer&&e.streamIdleTimer.unref()}sendTurnUpdate(e,t){const s=resolveTurnId(e);return s&&void 0===t._turnId&&(t._turnId=s),recordTurnIdForRole(e,t,s),recordToolCallTurnForUpdate(e,t,s),!!e.sessionLoadInProgress||this.transport.sendSessionUpdate(e.sessionId,t,{turnId:s})}sendToolCallSpacer(e,t){const s={sessionUpdate:"agent_message_chunk",content:{type:"text",text:"\n"},...t?{timestamp:t}:{}};this.sendTurnUpdate(e,s),e.assistantMq?.push(s)}resolveToolCallId(e,t,s){const o=asString(t.toolCallId)??asString(t.tool_call_id)??asString(t.callId)??asString(t.id);if(o){const t=this.state.toolCallIdMap.get(o);return t?(e.sessionId&&this.state.toolCallToSessionMap.set(t,e.sessionId),t):(this.state.toolCallIdMap.set(o,o),e.sessionId&&this.state.toolCallToSessionMap.set(o,e.sessionId),o)}const n=`tc_${e.currentRunId??"unknown"}_${this.state.nextToolCallSeq++}`;return e.sessionId&&this.state.toolCallToSessionMap.set(n,e.sessionId),n}handleChatEvent(e){const t=(e.runId?this.state.promptsByRunId.get(e.runId):void 0)??this.state.findSessionByKey(e.sessionKey);if(!t)return;if(!this.state.isCurrentStreamOwner(t,e.runId)){const s=!t.currentRunId&&void 0===t.pendingPromptId;if(!e.runId||!s||this.state.isRunCompleted(e.runId))return;if(!this.adoptBackgroundRun(t,e.runId))return}t.sessionId;const s=e.message;if(s){this.touchStreamActivity(t);if("assistant"===s.role){if(!Array.isArray(s.content))return;for(const o of s.content){const n=o;if(!isRecord(n))continue;const a=asString(n.type);if(!a)continue;const i=toISO(s.timestamp);if("thinking"===a){if(t.hasAgentThinkingStream)continue;const e=normalizeText(n.thinking)??normalizeText(n.text);if(!e)continue;const s=dedupKey("chat:thinking",e);if(t.chatReplayDedupKeys.has(s))continue;t.chatReplayDedupKeys.add(s);const o={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:e},...i?{timestamp:i}:{}};this.sendTurnUpdate(t,o)&&t.assistantMq?.push(o);continue}if("toolCall"===a){if(this.sendToolCallSpacer(t,i),t.hasAgentToolStartStream)continue;const e=this.resolveToolCallId(t,n,"start"),s=asString(n.name)??asString(n.toolName)??asString(n.tool_name)??"tool",o=resolveToolArgs(n),a=dedupKey("chat:tool_start",e,JSON.stringify(o));if(t.chatReplayDedupKeys.has(a))continue;t.chatReplayDedupKeys.add(a);const r=buildToolCallPayload({toolCallId:e,toolName:s,args:o,timestamp:i});this.sendTurnUpdate(t,r)&&t.assistantMq?.push(r);continue}if("toolResult"===a){if(this.sendToolCallSpacer(t,i),t.hasAgentToolResultStream)continue;const e=this.resolveToolCallId(t,n,"result"),s=asString(n.name)??asString(n.toolName)??asString(n.tool_name)??"tool",o=n.result,a=normalizeText(n.text)??normalizeText(n.result)??(isRecord(o)||Array.isArray(o)?JSON.stringify(o,null,2):void 0);if(!a)continue;const r=dedupKey("chat:tool_result",e,a);if(t.chatReplayDedupKeys.has(r))continue;t.chatReplayDedupKeys.add(r),this.emitNormalizedToolResult(t,{toolCallId:e,toolName:s,rawText:a,rawResult:o,timestamp:i}).catch(e=>{this.logger.warn?.(`[gateway-events] Tool result normalization failed: ${e instanceof Error?e.message:e}`)});continue}if("text"===a&&n.text){if(t.hasAgentAssistantStream||t.agentAssistantTextSoFar)continue;const s=stripTransportMetadata(n.text);if(!s)continue;const o=classifyHeartbeatFilter(s);if(o.filtered){this.logHeartbeatSkip({source:"chat-text",reason:o.reason,sessionId:t.sessionId,runId:e.runId,cumulative:s});continue}const a=dedupKey("chat:text",s);if(t.chatReplayDedupKeys.has(a))continue;t.chatReplayDedupKeys.add(a);const r=t.chatAssistantTextSoFar??"";let l;if(t.chatAssistantTextSoFar=s,s.startsWith(r))l=s.slice(r.length);else{if(r.startsWith(s))continue;l=s}if(!l)continue;this.logger.debug?.(`[gateway-events] chat.text delta seq=${e.seq} runId=${e.runId} len=${l.length} prevLen=${r.length}`);const c={sessionUpdate:"agent_message_chunk",content:{type:"text",text:l},...i?{timestamp:i}:{}};this.sendTurnUpdate(t,c),t.assistantMq?.push(c)}}}}else"delta"===e.state&&this.touchStreamActivity(t);if(this.maybeCompletePromptFromChatState(t,e),!t.promptDone)if("final"===e.state)this.observeLifecycle(this.completePrompt(t,e.stopReason||"end_turn"),"chat_final",t);else if("aborted"===e.state||"cancelled"===e.state||"cancel"===e.state)this.observeLifecycle(this.completePrompt(t,"cancelled"),"chat_cancelled",t);else if("error"===e.state){const s=e.errorMessage||Errors.AgentError.message,o=classifyAgentRuntimeFailure({raw:s,source:"chat_state",runtimeKind:"openclaw"});"interrupted"===o.kind?this.observeLifecycle(this.interruptPrompt(t,o),"chat_interrupt",t):this.observeLifecycle(this.failPrompt(t,Errors.AgentError.rpcCode,o.publicMessage),"chat_failure",t)}}handleAgentEvent(e){const t=(e.runId?this.state.promptsByRunId.get(e.runId):void 0)??(e.sessionKey?this.state.findSessionByKey(e.sessionKey):void 0);if(!t)return;if(!this.state.isCurrentStreamOwner(t,e.runId)){const s=!t.currentRunId&&void 0===t.pendingPromptId;if(!e.runId||!s||this.state.isRunCompleted(e.runId))return;if(!this.adoptBackgroundRun(t,e.runId))return}e.runId&&t.cronJobId&&!this.state.runIdToCronJobId.has(e.runId)&&this.state.setRunCronJobId(e.runId,t.cronJobId);t.sessionId;this.touchStreamActivity(t);const s=toISO(e.ts),o=e=>{this.sendTurnUpdate(t,e),t.assistantMq?.push(e)};if("assistant"===e.stream){t.hasAgentAssistantStream=!0;const n=this.state.extractAgentAssistantDelta(t,e.data??{});if(n){const a=normalizeTransportText(n);if(a){const n=t.agentAssistantTextSoFar??a,i=classifyHeartbeatFilter(n);i.filtered?this.logHeartbeatSkip({source:"assistant-delta",reason:i.reason,sessionId:t.sessionId,runId:e.runId,cumulative:n,delta:a}):(this.logger.debug?.(`[gateway-events] agent.assistant delta seq=${e.seq} runId=${e.runId} len=${a.length} source=${e.data?.delta?"delta":"cumulative"}`),o({sessionUpdate:"agent_message_chunk",content:{type:"text",text:a},...s?{timestamp:s}:{}}))}}const a=[],i=e.data;i&&(Array.isArray(i.content)?a.push(...i.content):isRecord(i.content)?a.push(i.content):"string"==typeof i.type&&a.push(i));for(const e of a){if(!isRecord(e))continue;const n=asString(e.type);if("thinking"===n){t.hasAgentThinkingStream=!0;const o=normalizeText(e.thinking)??normalizeText(e.text);if(o){const e={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:o},...s?{timestamp:s}:{}};this.sendTurnUpdate(t,e)&&t.assistantMq?.push(e)}continue}if("toolCall"===n){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const n=this.resolveToolCallId(t,e,"start"),a=asString(e.name)??asString(e.toolName)??asString(e.tool_name)??"tool",i=resolveToolArgs(e);o(buildToolCallPayload({toolCallId:n,toolName:a,args:i,timestamp:s}));continue}if("toolResult"===n){this.sendToolCallSpacer(t,s),t.hasAgentToolResultStream=!0;const n=this.resolveToolCallId(t,e,"result"),a=asString(e.name)??asString(e.toolName)??asString(e.tool_name)??"tool",i=e.result,r=normalizeText(e.text)??normalizeText(e.result)??(isRecord(i)||Array.isArray(i)?JSON.stringify(i,null,2):void 0);r&&this.emitNormalizedToolResult(t,{toolCallId:n,toolName:a,rawText:r,rawResult:i,timestamp:s,pushUpdate:o}).catch(e=>{this.logger.warn?.(`[gateway-events] Tool result normalization failed: ${e instanceof Error?e.message:e}`)});continue}}}else if("thinking"===e.stream){t.hasAgentThinkingStream=!0;const o=normalizeText(e.data?.delta)??normalizeText(e.data?.text);if(o){const e={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:o},...s?{timestamp:s}:{}};this.sendTurnUpdate(t,e)&&t.assistantMq?.push(e)}}else if("tool"===e.stream){const n=e.data,a="string"==typeof n?.phase?n.phase:void 0;if("start"===a){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const e=this.resolveToolCallId(t,n,"start"),a=asString(n?.name)??asString(n?.toolName)??asString(n?.tool_name)??"tool",i=resolveToolArgs(n);o(buildToolCallPayload({toolCallId:e,toolName:a,args:i,timestamp:s}))}else if("result"===a)this.sendToolCallSpacer(t,s),t.hasAgentToolResultStream=!0,this.handleToolResult(t,e,s).catch(e=>{this.logger.warn?.(`[gateway-events] Tool result handling failed: ${e instanceof Error?e.message:e}`)});else{const e=n?.toolCall;if(e){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const a=asString(e?.name)??asString(n?.name)??asString(n?.toolName)??"tool",i={...n,...e},r=this.resolveToolCallId(t,i,"start"),l=resolveToolArgs(i);o(buildToolCallPayload({toolCallId:r,toolName:a,args:l,timestamp:s}))}}}else if("lifecycle"===e.stream){const s=e.data,o="string"==typeof s?.phase?s.phase:void 0,n=(o,n)=>{this.logger.warn?.(`[gateway-events] lifecycle error runId=${e.runId} sessionKey=${e.sessionKey} message=${o} data=${JSON.stringify(s)}`);const a=classifyAgentRuntimeFailure({raw:o,source:"live_gateway",runtimeKind:"openclaw"});"interrupted"===a.kind?this.observeLifecycle(this.interruptPrompt(t,a),"agent_interrupt",t):this.observeLifecycle(this.failPrompt(t,Errors.AgentError.rpcCode,a.publicMessage),n,t)};if("end"===o){const e=lifecycleEndFailureMessage(s);if(e)n(e,"agent_failure");else{const e="string"==typeof s?.stopReason?s.stopReason:void 0;this.observeLifecycle(this.completePrompt(t,e||"end_turn"),"agent_final",t)}}else if("cancelled"===o||"cancel"===o)this.observeLifecycle(this.completePrompt(t,"cancelled"),"agent_cancelled",t);else if("error"===o){const e=s?.error,t=e&&"object"==typeof e?e:void 0;n(("string"==typeof s?.message?s.message:void 0)??("string"==typeof e?e:void 0)??("string"==typeof t?.message?t.message:void 0)??("string"==typeof s?.reason?s.reason:void 0)??(t?JSON.stringify(t):void 0)??"gateway lifecycle error","agent_failure")}}}async handleToolResult(e,t,s){const o=t.data,n=o?.toolCall,a=("string"==typeof n?.id?n.id:void 0)??("string"==typeof o?.toolCallId?o.toolCallId:void 0),i=(a?this.state.toolCallIdMap.get(a):void 0)??a??`tc_${t.runId}_${t.seq}`,r=("string"==typeof n?.name?n.name:void 0)??("string"==typeof o?.name?o.name:void 0)??"tool",l="string"==typeof o?.text?o.text:void 0,c=a??i,d=o?.result;await this.emitNormalizedToolResult(e,{toolCallId:i,normalizeToolCallId:c,toolName:r,rawText:l,rawResult:d,timestamp:s})}async emitNormalizedToolResult(e,t){const s=t.pushUpdate??(t=>{this.sendTurnUpdate(e,t)&&e.assistantMq?.push(t)});await emitNormalizedToolResultForTurn({session:e,envelope:{toolCallId:t.toolCallId,toolName:t.toolName,rawText:t.rawText,rawResult:t.rawResult,timestamp:t.timestamp},normalizeToolCallId:t.normalizeToolCallId,turnId:resolveTurnId(e),config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:this.getCanvasState,sendUpdate:e=>(s(e),!0)})}adoptBackgroundRun(e,t){this.logger.debug?.(`[gateway-events] Adopting background run ${t} on session ${e.sessionId} (prev=${e.currentRunId})`);if(!this.adoptBackgroundMutation(e,t).ok)return this.observeLifecycle(this.abortGatewayRun(e.gatewaySessionKey,t),"background_run_conflict_abort",e),!1;e.currentRunId=t,e.promptDone=!1,this.state.promptsByRunId.set(t,e);const s=this.state.runIdToCronJobId.get(t);return s&&(e.cronJobId=s),e.assistantStreamStartedAt=Date.now(),e.lastStreamActivityTs=Date.now(),e.hasAgentAssistantStream=void 0,e.hasAgentThinkingStream=void 0,e.hasAgentToolStartStream=void 0,e.hasAgentToolResultStream=void 0,e.chatAssistantTextSoFar=void 0,e.agentAssistantTextSoFar=void 0,e.chatReplayDedupKeys?.clear(),e.presentedFileArtifactKeys=new Set,e.emittedToolResultKeys=new Set,e.assistantMq&&!e.assistantMq.closed||(e.assistantMq=new MessageQueue),this.schedulePromptTimeout(e),!0}handleCronEvent(e){this.transport.sendNotification("_beeos/cron_event",{payload:e});const t=e??{};"string"==typeof t.runId&&"string"==typeof t.jobId&&this.state.setRunCronJobId(t.runId,t.jobId),this.config.agentGatewayUrl&&this.agentFetchFn&&this.reportCronEventToAgentGateway(t),"added"===t.action&&this.maybeBackfillBeeosChannel(t),"finished"===t.action&&"string"==typeof t.summary&&t.summary&&this.publishCronResult(t)}async reportCronEventToAgentGateway(e){if(!this.config.agentGatewayUrl||!this.agentFetchFn)return;const t={...e},s="string"==typeof e.sessionKey?e.sessionKey:void 0;s&&null==t.sessionKey&&(t.sessionKey=s);const o=s?this.state.findSessionByKey(s)?.sessionId:void 0;if(o&&(t.sessionId=o),("string"!=typeof t.name||!t.name)&&"string"==typeof e.jobId&&e.jobId&&this.gatewayRequest)try{const s=await this.gatewayRequest("cron.list",{}),o=s?.jobs;if(Array.isArray(o))for(const s of o)if(isRecord(s)&&s.id===e.jobId&&"string"==typeof s.name&&s.name){t.name=s.name;break}}catch(e){this.logger.debug?.(`[gateway-events] cron.list lookup failed: ${e instanceof Error?e.message:e}`)}const n=`${this.config.agentGatewayUrl}/api/v1/agent/events`;try{await this.agentFetchFn(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"cron",data:t})})}catch(e){this.logger.warn?.(`[gateway-events] event report failed: ${e instanceof Error?e.message:e}`)}}async maybeBackfillBeeosChannel(e){const t="string"==typeof e.jobId?e.jobId:void 0;if(!t)return;if(!this.gatewayRequest)return;if(this.cronChannelCache?.get(t))return;let s;try{const e=await this.gatewayRequest("cron.list",{}),o=e?.jobs;if(Array.isArray(o))for(const e of o){if(!e||"object"!=typeof e)continue;const o=e;if(o.id===t){s=o;break}}}catch(e){return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel cron.list failed: ${e instanceof Error?e.message:e}`)}if(!s)return;const o="string"==typeof s.description?s.description:"";if(BEEOS_CHANNEL_TAG_RE.test(o)){const e=decodeBeeosChannelId(o);return void(e&&this.setCronChannelCacheLocal(t,e))}if("main"===("string"==typeof s.sessionTarget?s.sessionTarget:void 0))return;const n=isRecord(s.payload)?s.payload:void 0;if("systemEvent"===(n&&"string"==typeof n.kind?n.kind:void 0))return;const a=isRecord(s.delivery)?s.delivery:void 0;if(a&&"none"===a.mode)return;const i="string"==typeof s.sessionKey?s.sessionKey:void 0;if(!i)return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel: job has no sessionKey job=${t}`);const r=this.state.findSessionByKey(i),l=r?.channelId;if(!l)return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel: no channelId on ACPSession for sessionKey=${i} job=${t}`);const c=encodeBeeosChannelTag(o,l);try{await this.gatewayRequest("cron.update",{id:t,patch:{description:c}}),this.setCronChannelCacheLocal(t,l),this.logger.info?.(`[gateway-events] backfilled beeos:channel for LLM-created cron job=${t} channel=${l}`)}catch(e){this.logger.warn?.(`[gateway-events] maybeBackfillBeeosChannel cron.update failed: ${e instanceof Error?e.message:e}`)}}setCronChannelCacheLocal(e,t){this.cronChannelCache&&this.cronChannelCache.set(e,t)}async publishCronResult(e){const t=this.getMessageClient?.();if(!t)return;const s=this.getMsgPrincipalId?.();if(!s)return;const o="string"==typeof e.jobId?e.jobId:void 0;if(!o)return;const n=e.summary;let a=this.cronChannelCache?.get(o);if(!a&&this.gatewayRequest)try{const e=await this.gatewayRequest("cron.list",{}),t=e?.jobs;if(Array.isArray(t))for(const e of t){if(!e||"object"!=typeof e)continue;const t=e;if(t.id===o){a=decodeBeeosChannelId(t.description);break}}}catch(e){this.logger.debug?.(`[gateway-events] cron.list fallback failed: ${e instanceof Error?e.message:e}`)}if(!a)return;const i=randomUUID();try{await t.messages.send({conversationId:a,clientMessageId:i,idempotencyKey:i,type:"agent_reply",content:{text:n,cron_job_id:o,..."string"==typeof e.sessionKey?{session_key:e.sessionKey}:{}}}),this.logger.info?.(`[gateway-events] published cron result to channel=${a} job=${o}`)}catch(e){const t=e instanceof Error?e.message:String(e);if(isChannelGoneError(e,t))return this.logger.info?.(`[gateway-events] origin channel gone (session likely deleted), skipping publish: channel=${a} job=${o} err=${t}`),void this.cronChannelCache?.delete(o);this.logger.warn?.(`[gateway-events] publishCronResult failed: ${t}`)}}logHeartbeatSkip(e){const t=previewSanitizedText(e.cumulative),s=void 0!==e.delta?previewSanitizedText(e.delta):void 0;this.logger.info?.(`[gateway-events] SKIP heartbeat ${e.source} reason=${e.reason} sessionId=${e.sessionId} runId=${e.runId??"n/a"} cumLen=${e.cumulative.length}`+(void 0!==e.delta?` deltaLen=${e.delta.length}`:"")+` cumulative="${t}"`+(void 0!==s?` delta="${s}"`:"")),this.writeObs({component:"gateway-events",domain:"lifecycle",name:"heartbeat_filter_skip",severity:"info",summary:`heartbeat ${e.source} drop (${e.reason})`,payload:{source:e.source,reason:e.reason,sessionId:e.sessionId,runId:e.runId,cumulativeLen:e.cumulative.length,cumulativePreview:t,...void 0!==e.delta?{deltaLen:e.delta.length,deltaPreview:s}:{}}})}}export function isChannelGoneError(e,t){const s=t.toLowerCase();if(s.includes("not found")||s.includes("no such channel")||s.includes("channel does not exist")||s.includes("channel gone")||s.includes("410")||/\b404\b/.test(s))return!0;if(isRecord(e)){const t=e.status??e.statusCode??e.code;if(404===t||410===t)return!0}return!1}
|
package/dist/acp-server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{WebSocketServer,WebSocket}from"ws";import{randomUUID}from"node:crypto";import{AsyncLocalStorage}from"node:async_hooks";let BridgeClientCtor;import{GatewayClient}from"./gateway-client.js";import{TerminalManager}from"./terminal/session-manager.js";import{getOrCreateRuntimeTerminalService}from"./terminal/runtime-terminal-service.js";import{RuntimeTerminalOutboundRegistration}from"./terminal/runtime-terminal-outbound-registration.js";import{FileTerminalAuthorityJournal}from"./terminal/terminal-authority-journal.js";import{LocalTerminalAuthorityRegistry}from"./terminal/local-terminal-authority.js";import{TerminalLeaseBoundBridgeClient}from"./terminal/terminal-lease-bound-bridge-client.js";import{AgentGatewayHttpProjectionTransport,AgentGatewayTemplateOriginReporter,AgentProjectionProduction,OpenClawAgentProjectionSource}from"./runtime/agent-projection-production.js";import{join}from"node:path";import{AcpTransport}from"./acp-gateway/transport.js";import{AcpGatewaySessionState}from"./acp-gateway/session-state.js";import{AcpGatewayBridgeCore}from"./acp-gateway/bridge-core.js";import{BeeosFileResolver}from"./acp-gateway/beeos-file-resolver.js";import{AcpGatewayEvents}from"./acp-gateway/gateway-events.js";import{sweepStaleSessionLocks}from"./acp-gateway/stale-session-lock-sweeper.js";import{TerminalBridgeHandler}from"./terminal/bridge-handler.js";import{isAgentSyncWaitForGateway,syncAgentsToPlatform}from"./agent-sync.js";import{CanvasOrchestrator}from"./canvas-orchestrator.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{writeDiscoveryCache}from"./discovery-cache.js";import{MessageClient}from"@beeos-ai/message-sdk";function formatBridgeEventArgs(e){return 0===e.length?"(no args)":e.map((e,t)=>`arg${t}=${formatBridgeEventValue(e)}`).join(" ")}function formatBridgeEventValue(e){if(void 0===e)return"undefined";if(null===e)return"null";if(e instanceof Error){const t=e.stack?e.stack.split("\n").slice(0,4).join(" | "):"";return`Error(name=${e.name},message=${e.message}${t?`,stack=${t}`:""})`}if("object"==typeof e)try{return JSON.stringify(e,jsonReplacerForLogs)}catch{return String(e)}return String(e)}function jsonReplacerForLogs(e,t){return t instanceof Error?{name:t.name,message:t.message,stack:t.stack}:t}export class ACPServer{agentFetchFn;instanceId;runtimeAuthority;mcpToolBridge;mcpCredentialReferences;boundAgentFetchFn;wss=null;gateway=null;client=null;config;logger;writeObs;transport;state;core;events;terminalMgr;terminalHandler;localTerminalAuthorityRegistry;acpConnectionTerminalHandlers=new WeakMap;runtimeTerminalService;terminalOutbound=null;terminalServiceReady=!1;runtimeAgentCapabilities;agentProjection;runtimeRpcConsumer;runtimeRpcConsumerFactory;canvasOrch;sessionMutationCoordinator;messageClient;msgPrincipalId=null;pendingToolCallSessionKeys=[];thirdPartyChannels;agentSyncRetryTimer=null;agentSyncInFlight=!1;runtimeServiceFactoryFenced=!1;legacyFencedSyncFailureCount=0;lifecyclePhase="stopped";lifecycleGeneration=0;activeGeneration;bridgeInstance=null;bridgeClosePromises=new WeakMap;consumerClosePromises=new WeakMap;teardownContext=new AsyncLocalStorage;bridgeConsecutiveFailures=0;static RE_DISCOVER_THRESHOLD=2;static LIFECYCLE_CONNECT_TIMEOUT_MS=1e4;static LIFECYCLE_TEARDOWN_TIMEOUT_MS=5e3;drainNextDeferredSend(e){const t=e.pendingAgentSends?.shift();t&&(e.pendingAgentSends?.length||(e.pendingAgentSends=void 0),this.agentSend(t.message,t.attachments,e.gatewaySessionKey).catch(e=>this.logger.warn?.("[acp-server] Deferred agentSend failed: %s",e)))}async gatewayRequest(e,t){if(!this.gateway?.isConnected)throw new Error("Gateway not connected");return this.gateway.request(e,t)}async chatAbort(e,t){const i=this.gateway;if(!i?.isConnected)throw new Error("Gateway not connected");return i.chatAbort(e,t)}async agentSend(e,t,i){if(!this.gateway?.isConnected)throw new Error("Gateway not connected");const n=this.config.gateway.agentId||this.core.getDefaultAgentId();let r,s;i?(r=this.state.findSessionByKey(i),s=i,r?this.logger.info?.(`[acp-server] agentSend: resolved session for key=${i} sid=${r.sessionId}`):(r=this.core.ensureSessionForKey(s),this.logger.info?.(`[acp-server] agentSend: created session for key=${i} sid=${r.sessionId}`))):(s=`agent:${n}:ephemeral:${randomUUID()}`,r=this.core.ensureSessionForKey(s),this.logger.info?.(`[acp-server] agentSend: no targetSessionKey, created ephemeral session key=${s}`));const a=await this.sessionMutationCoordinator.acquireInternal({sessionKey:s,label:"agentSend"});let o=!1;try{const i=this.gateway;if(!i?.isConnected)throw new Error("Gateway not connected");if(r&&!r.promptDone&&(void 0!==r.pendingPromptId||void 0!==r.currentRunId||void 0!==r.gatewayRequestId))return(r.pendingAgentSends??=[]).push({message:e,attachments:t}),this.logger.info?.("[acp-server] Session has active run, deferred message (queue=%d)",r.pendingAgentSends.length),void this.sessionMutationCoordinator.releaseBeforeRuntime(a);o=!0;const c=await i.agentSend(s,e,n,t);return c?.runId&&r?(this.state.resetForNewRun(r,r.sessionId,this.transport),r.currentRunId=c.runId,this.sessionMutationCoordinator.bindRun(a,c.runId),this.core.observeActiveRun(r),r.assistantStreamStartedAt=Date.now(),r.writeGateToken=a,this.state.promptsByRunId.set(c.runId,r)):this.sessionMutationCoordinator.releaseBeforeRuntime(a),c}catch(e){throw o?this.sessionMutationCoordinator.requireRecovery(a,"terminal_not_observed"):this.sessionMutationCoordinator.releaseBeforeRuntime(a),e}}setMessageClient(e,t){this.messageClient.setConfig(e),this.msgPrincipalId=t,this.logger.info?.(`[acp-server] MessageEnvelope v3 streamer ready (principal=${t})`)}getMessageClient(){return this.messageClient}getRuntimeCommandDispatcher(){return this.core.getRuntimeCommandDispatcher()}getActiveRunAuthority(){return this.core.getActiveRunAuthority()}ensureImInboundSession(e,t,i){const n=this.core.ensureSessionForKey(e);n.channelId=t,n.inboundMessageId=i}sendBridgeMessage=e=>{const t=this.client;if(t&&t.readyState===WebSocket.OPEN)try{t.send(JSON.stringify(e))}catch(e){this.logger.debug?.("[acp-server] bridge send failed (socket may be closing):",e)}else this.logger.debug?.("[acp-server] bridge not ready, dropping message")};constructor(e,t,i,n,r,s,a,o,c,l,h,g,d){this.agentFetchFn=r,this.instanceId=s,this.runtimeAuthority=c,this.mcpToolBridge=h,this.mcpCredentialReferences=g,this.boundAgentFetchFn=d,this.config=e,this.logger=t,this.writeObs=n??(()=>{}),this.thirdPartyChannels=a??new Set,this.sessionMutationCoordinator=i,this.runtimeRpcConsumerFactory=l,this.runtimeAgentCapabilities={canvas:this.config.canvasEnabled,terminal:!1,webSsh:!1,fileSystem:!0,image:!0,audio:!1,mcp:!0,cron:!0,streaming:!0,pushNotifications:this.config.messageBus.enabled},this.runtimeAuthority?.setRuntimeRealtimeCapabilityReady("canvas",this.config.canvasEnabled),this.runtimeAuthority?.setRuntimeRealtimeCapabilityReady("terminal",!0===this.config.terminalServiceEnabled),this.transport=new AcpTransport(this.sendBridgeMessage,this.writeObs,void 0,()=>{const e=this.client;return!!e&&e.readyState===WebSocket.OPEN}),this.state=new AcpGatewaySessionState(this.logger),this.messageClient=new MessageClient({logger:this.logger});const u=this.agentFetchFn&&this.config.agentGatewayUrl?new BeeosFileResolver({agentGatewayUrl:this.config.agentGatewayUrl,agentFetch:this.agentFetchFn,writeObs:this.writeObs}):void 0,m=this.runtimeAuthority&&this.config.agentGatewayUrl?new AgentGatewayTemplateOriginReporter({agentGatewayUrl:this.config.agentGatewayUrl,boundAgentFetch:this.runtimeAuthority.boundAgentFetch}):void 0;this.core=new AcpGatewayBridgeCore({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,getGateway:()=>this.gateway,instanceId:this.instanceId,getAuthoritativeInstanceId:()=>process.env.BEEOS_INSTANCE_ID?.trim()||this.getDefaultPlatformAgent()?.instanceId,thirdPartyChannels:this.thirdPartyChannels,beeosFileResolver:u,agentFetch:this.agentFetchFn,agentGatewayUrl:this.config.agentGatewayUrl,sessionMutationCoordinator:i,triggerAgentSync:()=>this.tryAgentSync(),runtimeAgentIdentities:this.runtimeAuthority?.identities,runtimeCommandJournal:this.runtimeAuthority?.journal,markAgentProjectionDirty:this.runtimeAuthority?()=>this.agentProjection?.markDirty():void 0,getAgentProjection:this.runtimeAuthority?()=>this.agentProjection:void 0,templateOriginReporter:m,mcpToolBridge:this.mcpToolBridge,mcpCredentialReferences:this.mcpCredentialReferences}),this.runtimeAuthority&&this.agentFetchFn&&this.config.agentGatewayUrl&&(this.agentProjection=new AgentProjectionProduction({authority:this.runtimeAuthority,stateFile:join(this.runtimeAuthority.stateDirectory,"runtime-agent-projection.json"),journal:this.runtimeAuthority.journal,identities:this.runtimeAuthority.identities,source:new OpenClawAgentProjectionSource(()=>this.gateway),transport:new AgentGatewayHttpProjectionTransport({agentGatewayUrl:this.config.agentGatewayUrl,boundAgentFetch:this.runtimeAuthority.boundAgentFetch,getRuntimeLeaseCredential:e=>{const t=this.runtimeAuthority?.authorityLease;if(!t||t.instanceId!==e.instanceId||t.leaseId!==e.leaseId||t.runtimeEpoch!==e.runtimeEpoch||t.handlerIdentity!==e.handlerIdentity)throw new Error("agent projection fenced by current runtime lease");return t.runtimeLeaseCredential},onAccepted:e=>{e.size>0&&this.core.setAgentMapping(e)}}),capabilities:this.runtimeAgentCapabilities,logger:this.logger})),this.canvasOrch=new CanvasOrchestrator({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,getSessionIdForToolCall:e=>this.getSessionIdForToolCall(e),gateway:null,getDefaultAgentId:()=>this.core.getDefaultAgentId(),resolvePlatformAgentId:e=>this.core.resolvePlatformAgentId(e)??""}),this.events=new AcpGatewayEvents({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,completePrompt:(e,t)=>this.core.completePrompt(e,t),failPrompt:(e,t,i)=>this.core.failPrompt(e,t,i),interruptPrompt:(e,t)=>this.core.interruptPrompt(e,t),schedulePromptTimeout:e=>this.core.schedulePromptTimeout(e),adoptBackgroundMutation:(e,t)=>this.core.adoptBackgroundMutation(e,t),abortGatewayRun:async(e,t)=>{await this.chatAbort(e,t)},maybeCompletePromptFromChatState:(e,t)=>this.core.maybeCompletePromptFromChatState(e,t),agentFetch:this.agentFetchFn,getMessageClient:()=>this.messageClient,getMsgPrincipalId:()=>this.msgPrincipalId??null,getCanvasState:()=>this.canvasOrch.canvasState,gatewayRequest:(e,t)=>{const i=this.gateway;return i?.isConnected?i.request(e,t):Promise.reject(new Error("gateway not connected"))},cronChannelCache:this.core.cronChannelCache}),this.core.onRunEnded=(e,t)=>{"complete"===t.kind?this.canvasOrch.onPromptComplete(e):this.canvasOrch.onPromptFailed(),this.drainNextDeferredSend(e)},this.terminalMgr=o??new TerminalManager,this.localTerminalAuthorityRegistry=new LocalTerminalAuthorityRegistry(this.terminalMgr),this.terminalHandler=this.createTerminalHandler(this.transport),this.runtimeTerminalService=getOrCreateRuntimeTerminalService(this.terminalMgr),this.core.setRuntimeTerminalService(this.runtimeTerminalService),this.core.handleTerminalRequest=e=>this.terminalHandler.handleRequest(e),this.core.handleTerminalNotification=(e,t)=>this.terminalHandler.handleNotification(e,t),this.core.handleCanvasNotification=(e,t)=>{this.canvasOrch.handleCanvasNotification(e,t)},this.core.getCanvasState=()=>this.canvasOrch.canvasState,this.core.onSessionLoaded=(e,t,i)=>{this.canvasOrch.onSessionLoaded(e,t,i)},this.core.onSkillInstalled=(e,t)=>{this.registerSkillFormSchemas(e,t)},this.core.onSessionDeleted=e=>{this.canvasOrch.onSessionDeleted(e)},this.core.onSessionCleared=(e,t)=>{this.canvasOrch.onSessionCleared(e,t)},this.core.handleSlashCommand=async e=>this.canvasOrch.handleSlashCommand(e),this.core.onPromptStart=()=>{this.canvasOrch.onPromptStart()},this.state.startSessionGc(e=>{e.writeGateToken&&this.sessionMutationCoordinator.requireRecovery(e.writeGateToken,"terminal_not_observed"),this.core.cleanupPrompt(e)},e=>{this.canvasOrch.canvasState?.removeSession(e.gatewaySessionKey),this.transport.resetSessionUpdateCount(e.sessionId)})}setRuntimeRpcConsumerFactory(e){if(e!==this.runtimeRpcConsumerFactory){if("stopped"!==this.lifecyclePhase)throw new Error("runtime Service factory is immutable after ACP start");this.runtimeRpcConsumerFactory=e}}start(e){const t=this.activeGeneration;if("starting"===this.lifecyclePhase||"started"===this.lifecyclePhase)return this.logger.info?.("[acp-server] start() skipped — already started (singleton reuse)"),t?.startPromise??Promise.resolve();if("stopping"===this.lifecyclePhase){return(t?.stopPromise??Promise.resolve()).then(()=>this.start(e),()=>this.start(e))}const i={id:++this.lifecycleGeneration,factory:this.runtimeRpcConsumerFactory,authority:this.runtimeAuthority,abortController:new AbortController,startPromise:Promise.resolve(),authorityOwned:!1,projectionStarted:!1};return this.lifecyclePhase="starting",this.activeGeneration=i,i.startPromise=this.startGeneration(i,e).then(()=>{this.activeGeneration===i&&"starting"===this.lifecyclePhase&&(this.lifecyclePhase="started")}).catch(async e=>{try{await this.teardownGeneration(i)}catch(e){this.logger.debug?.(`[acp-server] failed-start teardown also failed: ${e instanceof Error?e.message:String(e)}`)}finally{this.activeGeneration===i&&"starting"===this.lifecyclePhase&&(this.activeGeneration=void 0,this.lifecyclePhase="stopped")}throw e}),i.startPromise}async startGeneration(e,t){const i=e.authority,n=e.factory;let r=!1;if(i&&n){e.authorityOwned=!0;const t=await this.waitForGeneration(i.prepareConsumerBinding(),e);if("completed"!==t.status||!this.shouldContinueStart(e))return;const s=n.create({dispatcher:this.core.getRuntimeCommandDispatcher(),binding:t.value,getBinding:()=>i.currentConsumerBinding()??t.value,getLease:()=>i.authorityLease,signal:e.abortController.signal}),a=await this.waitForGeneration(s,e);if("completed"!==a.status)return void("cancelled"===a.status&&s.then(e=>this.closeConsumerOnce(e),()=>{}));const o=a.value;if(!this.shouldContinueStart(e))return void this.closeConsumerOnce(o,e.abortController.signal).catch(()=>{});if(this.core.setRuntimeReceiptHistoryPort(o.receiptHistory),e.runtimeConsumer=o,this.runtimeRpcConsumer=o,o.start(),!this.shouldContinueStart(e))return;i.setRuntimeConsumerLive(!0),r=!0,this.runtimeServiceFactoryFenced=!1}else i&&(e.authorityOwned=!0),i?.setRuntimeConsumerLive(!1),i&&(this.runtimeServiceFactoryFenced=!0,this.logger.warn?.("[acp-server] runtime Service factory unavailable — registration fenced"));if(this.core.startMcp().catch(e=>{this.logger.warn?.(`[acp-server] MCP desired-state reconcile failed: ${e instanceof Error?e.message:String(e)}`)}),!this.shouldContinueStart(e))return;if(i&&r){if("completed"!==(await this.waitForGeneration(i.start(e.abortController.signal),e)).status||!this.shouldContinueStart(e))return}this.agentProjection&&(e.projectionStarted=!0,this.agentProjection.start());const s=Date.now();this.logger.info?.("[acp-server] start() begin"),t&&(this.config.bridge.url=t);const a=this.config.bridge.url,o=this.config.keyFile;if(a&&!BridgeClientCtor)try{const t=Date.now(),i=await this.waitForGeneration(import("@beeos-ai/bridge-client"),e);if("completed"!==i.status)return;BridgeClientCtor=i.value.BridgeClient,this.logger.info?.(`[acp-server] bridge-client import took ${Date.now()-t}ms`)}catch(e){this.logger.warn?.("[acp-server] Bridge SDK not available")}if(!this.shouldContinueStart(e))return;if(!0===this.config.terminalServiceEnabled&&a&&i){const t=new TerminalLeaseBoundBridgeClient({bridgeUrl:a,authority:i,keys:i.agentKeys}),n=new RuntimeTerminalOutboundRegistration({client:t,manager:this.terminalMgr,service:this.runtimeTerminalService,authorityJournal:new FileTerminalAuthorityJournal(join(i.stateDirectory,"terminal-authority-journal.json")),onAvailabilityChange:t=>this.setTerminalServiceReady(e,t),logger:this.logger});if(!this.shouldContinueStart(e))return void n.drain({timeoutMs:5e3,terminateAfterTimeout:!0,signal:e.abortController.signal}).catch(()=>{});e.terminalOutbound=n,this.terminalOutbound=n;try{const t=await this.waitForGeneration(n.start(),e,ACPServer.LIFECYCLE_CONNECT_TIMEOUT_MS);if("cancelled"===t.status)return;if("timed_out"===t.status)throw new Error("Terminal connection timed out")}catch(e){this.logger.warn?.(`[terminal-service] initial outbound registration failed; no legacy fallback: ${e instanceof Error?e.message:String(e)}`)}if(!this.shouldContinueStart(e))return}else!0!==this.config.terminalServiceEnabled||this.runtimeAuthority||this.logger.warn?.("[terminal-service] disabled: active Runtime registration authority unavailable");const c=Date.now();if(await this.connectGateway(e),this.shouldContinueStart(e)){this.logger.info?.(`[acp-server] connectGateway() took ${Date.now()-c}ms`);try{const t=await this.waitForGeneration(sweepStaleSessionLocks({logger:this.logger}),e);if("completed"!==t.status)return;const i=t.value;this.logger.info?.(`[acp-server] stale lock sweep scanned=${i.scanned} quarantined=${i.quarantined} skipped=${i.skipped}`)}catch(e){this.logger.warn?.(`[acp-server] stale lock sweep failed: ${e instanceof Error?e.message:String(e)}`)}if(this.shouldContinueStart(e)){if(a&&o&&BridgeClientCtor){Date.now();await this.startBridgeMode(a,o,e)}else this.logger.warn?.("[acp-server] Bridge not configured; running in standby (no ACP listener). Use handleUpgrade() for noServer mode."),this.core.markAgentSyncDone();this.logger.info?.(`[acp-server] start() total ${Date.now()-s}ms`)}}}shouldContinueStart(e){return!e.abortController.signal.aborted&&this.activeGeneration===e&&"starting"===this.lifecyclePhase}isGenerationActive(e){return!e.abortController.signal.aborted&&this.activeGeneration===e&&("starting"===this.lifecyclePhase||"started"===this.lifecyclePhase)}setTerminalServiceReady(e,t){if(this.activeGeneration!==e)return;const i=!0===this.config.terminalServiceEnabled&&t;this.terminalServiceReady!==i&&(this.terminalServiceReady=i,this.runtimeAgentCapabilities.terminal=i,this.agentProjection?this.agentProjection.markDirty():this.tryAgentSync())}async waitForGeneration(e,t,i){const n=t.abortController.signal;return n.aborted?(e.catch(()=>{}),{status:"cancelled"}):await new Promise((t,r)=>{let s,a=!1;const o=e=>{a||(a=!0,n.removeEventListener("abort",c),s&&clearTimeout(s),t(e))},c=()=>o({status:"cancelled"});n.addEventListener("abort",c,{once:!0}),void 0!==i&&(s=setTimeout(()=>o({status:"timed_out"}),i),s.unref?.()),e.then(e=>o({status:"completed",value:e}),e=>{a||(a=!0,n.removeEventListener("abort",c),s&&clearTimeout(s),r(e))})})}closeConsumerOnce(e,t){const i=this.consumerClosePromises.get(e);if(i)return i;const n=Promise.resolve().then(()=>e.stop(t));return this.consumerClosePromises.set(e,n),n}async startBridgeMode(e,t,i){if(!BridgeClientCtor)throw new Error("@beeos-ai/bridge-client not available");if(!this.isGenerationActive(i))return;const n=new BridgeClientCtor({bridgeUrl:e,service:this.config.bridge.service||"acp",keyFile:t});if(!this.isGenerationActive(i))return void this.closeBridgeOnce(n,!1).catch(()=>{});i.bridge=n,this.bridgeInstance=n;const r=()=>this.bridgeInstance===n;n.on("connected",()=>{r()&&(this.terminalHandler.destroy(),this.terminalHandler=this.createTerminalHandler(this.transport),this.bridgeConsecutiveFailures=0,this.logger.info?.("[acp-server] Connected to Bridge (publicKey auth)"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"bridge.connected",severity:"info"}),this.tryAgentSync())}),n.on("disconnected",(...e)=>{if(!r())return;this.terminalHandler.destroy();const s=String(e[0]??"unknown"),a=formatBridgeEventArgs(e);if(this.logger.warn?.(`[acp-server] Bridge disconnected: ${s} (detail=${a})`),this.core.initialized=!1,this.bridgeConsecutiveFailures++,this.bridgeConsecutiveFailures>=ACPServer.RE_DISCOVER_THRESHOLD&&this.agentFetchFn&&this.config.agentGatewayUrl){this.bridgeConsecutiveFailures=0;const e=this.config.bridge.url;discoverBridgeFromGW(this.config.agentGatewayUrl,this.agentFetchFn).then(async s=>{if(this.isGenerationActive(i)&&r()&&s&&s.url!==e){if(this.logger.info?.(`[acp-server] Bridge re-discovery: URL changed ${e} → ${s.url}, switching`),this.config.bridge.url=s.url,writeDiscoveryCache({bridge:{url:s.url,discoveredAt:Date.now()}}),await this.closeBridgeOnce(n,!1),!this.isGenerationActive(i)||this.bridgeInstance!==n)return;i.bridge===n&&(i.bridge=void 0),this.activeGeneration!==i||"starting"!==this.lifecyclePhase&&"started"!==this.lifecyclePhase||await this.startBridgeMode(s.url,t,i)}}).catch(()=>{})}}),n.on("message",(...e)=>{if(!r())return;const t=e[0],i="string"==typeof t?t:Buffer.from(t).toString("utf-8");try{const e=JSON.parse(i);"id"in e&&void 0!==e.id?this.core.handleRequest(e).catch(e=>{this.logger.error?.(`[acp-server] Bridge request failed: ${e instanceof Error?e.message:String(e)}`)}):this.core.handleNotification(e)}catch{this.logger.error?.("[acp-server] Invalid JSON-RPC message from Bridge")}}),n.on("error",(...e)=>{if(!r())return;const t=formatBridgeEventArgs(e);this.logger.error?.(`[acp-server] Bridge error: ${t}`)});const s={readyState:WebSocket.OPEN,send:(e,t)=>{n.send(e).then(()=>t?.(),e=>{const i=e instanceof Error?e:new Error(String(e));this.logger.warn?.(`[acp-server] bridge.send failed: ${i.message}`),t?.(i)})},close:()=>{this.closeBridgeOnce(n).catch(e=>{this.logger.debug?.(`[acp-server] bridge.close failed: ${e instanceof Error?e.message:String(e)}`)})},terminate:()=>{this.closeBridgeOnce(n,!1).catch(e=>{this.logger.debug?.(`[acp-server] bridge.terminate failed: ${e instanceof Error?e.message:String(e)}`)})},on:()=>s,once:()=>s,removeListener:()=>s,removeAllListeners:()=>s,ping:()=>{}};Object.defineProperty(s,"readyState",{get:()=>"connected"===n.state?WebSocket.OPEN:WebSocket.CLOSED}),i.bridgeSocket=s,this.terminalHandler.destroy(),this.terminalHandler=this.createTerminalHandler(this.transport),this.client=s;const a=Date.now();try{const e=await this.waitForGeneration(n.connect(),i,ACPServer.LIFECYCLE_CONNECT_TIMEOUT_MS);if("cancelled"===e.status)return;if("timed_out"===e.status)throw new Error("Bridge connection timed out");this.logger.info?.(`[acp-server] bridge.connect() took ${Date.now()-a}ms`)}catch(e){this.logger.warn?.(`[acp-server] Initial bridge connect failed after ${Date.now()-a}ms (will retry in background): ${e?.message}`)}this.core.initialized=!1,this.logger.info?.("[acp-server] Running in Bridge mode (WS client)")}closeBridgeOnce(e,t){const i=this.bridgeClosePromises.get(e);if(i)return i;const n=Promise.resolve().then(()=>e.close(t));return this.bridgeClosePromises.set(e,n),n}handleUpgrade(e,t,i){this.wss||(this.wss=new WebSocketServer({noServer:!0}),this.wss.on("connection",(e,t)=>this.handleConnection(e,t))),this.wss.handleUpgrade(e,t,i,t=>{this.wss.emit("connection",t,e)})}async tryAgentSync(e=0){if(this.agentSyncRetryTimer&&(clearTimeout(this.agentSyncRetryTimer),this.agentSyncRetryTimer=null),!this.client||this.client.readyState!==WebSocket.OPEN)return;if(!this.gateway||!this.agentFetchFn||!this.config.agentGatewayUrl)return void this.core.markAgentSyncDone();if(this.agentProjection){if(this.runtimeAuthority?.authorityLease)return void this.agentProjection.onGatewayReconnect();if(!this.runtimeServiceFactoryFenced)return;0===e&&this.logger.error?.("[acp-server] runtime Service factory fenced with no authority lease — attempting one-shot legacy agent-sync bootstrap to recover Agent Identity")}const t=!!this.agentProjection;if(t||(this.legacyFencedSyncFailureCount=0),this.agentSyncInFlight)return;const i=[1e3,2e3,5e3,1e4],n=3e5,r=()=>{const r=e+1;if(t&&(this.legacyFencedSyncFailureCount+=1,this.legacyFencedSyncFailureCount>=4)){const e=Math.round(5);return this.logger.error?.(`[acp-server] legacy agent-sync bootstrap fallback has failed ${this.legacyFencedSyncFailureCount} consecutive times while the runtime Service factory is fenced — this fallback path is confirmed incompatible with a backend that requires the runtime-projection agent-sync schema and cannot self-heal; restart this instance once Message Service / the runtime Service factory recover. Backing off to a ${e}m retry interval to stop busy-looping a request that cannot succeed.`),void(this.agentSyncRetryTimer=setTimeout(()=>{this.tryAgentSync(r)},n))}const s=i[Math.min(e,i.length-1)],a=Math.round(s/1e3),o=`[acp-server] agent sync retry #${r} in ${a}s`;r>0&&r%30==0?this.logger.warn?.(`[acp-server] agent sync still failing after ${r} attempts — retrying every ${a}s`):r<=i.length?this.logger.info?.(o):this.logger.debug?.(o),this.agentSyncRetryTimer=setTimeout(()=>{this.tryAgentSync(r)},s)},s=Date.now();this.agentSyncInFlight=!0;try{const e=await syncAgentsToPlatform({gateway:this.gateway,agentGatewayUrl:this.config.agentGatewayUrl,capabilities:{canvas:this.config.canvasEnabled,terminal:this.terminalServiceReady,fileSystem:!0,cron:!0,streaming:!0},deliveryMode:this.config.messageBus.enabled?"push":void 0,boundAgentFetch:this.boundAgentFetchFn,logger:this.logger});isAgentSyncWaitForGateway(e)?this.logger.info?.("[acp-server] agent sync waiting for OpenClaw Gateway connection"):e?(this.legacyFencedSyncFailureCount=0,this.core.setAgentMapping(e),this.logger.info?.(`[acp-server] agent mapping loaded: ${[...e.keys()].join(", ")} (sync took ${Date.now()-s}ms)`),this.agentProjection&&this.runtimeServiceFactoryFenced&&this.logger.error?.("[acp-server] agent bootstrap sync recovered Agent Identity while the runtime Service factory was fenced — restart this instance to complete registration")):r()}catch(e){this.logger.warn?.(`[acp-server] agent sync failed: ${e}`),r()}finally{this.agentSyncInFlight=!1}}async connectGateway(e){const t=this.config.gateway,i=new GatewayClient({url:t.url,token:t.token||"",clientId:t.clientId,openclawCanvasCompat:!!this.config.openclawCanvasCompat,retry:{baseMs:this.config.retry.baseMs,maxMs:this.config.retry.maxMs,maxAttempts:this.config.retry.maxAttempts}});if(e&&!this.shouldContinueStart(e))return i.stop(),void i.removeAllListeners();e&&(e.gateway=i),this.gateway=i,i.on("chat",e=>this.events.handleChatEvent(e)),i.on("agent",e=>this.events.handleAgentEvent(e)),i.on("cron",e=>this.events.handleCronEvent(e)),i.on("sessionMessage",e=>this.core.handleSessionMessage(e)),i.on("response",e=>this.core.handleGatewayResponse(e));const n=i;this.canvasOrch.updateDeps({gateway:n?{get isConnected(){return n.isConnected},request:(e,t)=>this.gateway.request(e,t),agentSend:(e,t,i)=>this.gateway.agentSend(e,t,i)}:null}),this.config.openclawCanvasCompat&&(i.on("canvas",e=>this.canvasOrch.handleOpenClawCanvasEvent(e)),this.logger.info?.("[acp-server] OpenClaw native canvas compat enabled — listening for canvas events")),i.on("connected",()=>{this.agentProjection?.onGatewayReconnect(),this.logger.info?.("[acp-server] Connected to OpenClaw Gateway"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"gateway.connected",severity:"info"}),this.core.resubscribeAllSessionMessages(),this.core.refreshCronIsolatedSessionKeys(),this.tryAgentSync()}),i.on("disconnected",()=>{this.logger.warn?.("[acp-server] Gateway disconnected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"gateway.disconnected",severity:"warn"}),this.core.cleanupAllPendingPrompts()}),i.on("reconnecting",e=>{this.logger.info?.(`[acp-server] Reconnecting to Gateway attempt=${e.attempt} delay=${Math.round(e.delayMs)}ms`)}),i.on("reconnect-exhausted",e=>{this.logger.error?.(`[acp-server] Gateway reconnect exhausted after ${e} attempts`)}),i.on("error",e=>{this.logger.error?.("[acp-server] Gateway error:",e.message)}),i.on("transport",e=>{this.logger.info?.(`[acp-server] transport.${e.event} durationMs=${e.durationMs??"?"}`),this.writeObs({component:"gateway-client",domain:"lifecycle",name:`transport.${e.event}`,severity:"connect_fail"===e.event||"error"===e.event?"warn":"debug",payload:e})}),i.on("reconnect-notify-scheduled",e=>{this.logger.info?.(`[acp-server] Server-initiated reconnect scheduled in ${e.delayMs}ms`)}),i.on("reconnect-notify-fired",()=>{this.logger.info?.("[acp-server] Server-initiated reconnect firing")});try{if(e){const t=await this.waitForGeneration(i.start(),e,ACPServer.LIFECYCLE_CONNECT_TIMEOUT_MS);if("cancelled"===t.status)return;if("timed_out"===t.status)throw new Error("Gateway connection timed out")}else await i.start()}catch(e){this.logger.error?.("[acp-server] Initial Gateway connect failed:",e)}}handleConnection(e,t){if(this.client){this.logger.warn?.("[acp-server] Replacing existing ACP client with new connection (takeover)");const e=this.client;this.client=null;const t=this.acpConnectionTerminalHandlers.get(e);t?t.destroy():this.terminalHandler.destroy(),this.acpConnectionTerminalHandlers.delete(e);try{e.close(1008,"replaced by new client")}catch{e.terminate()}}this.client=e;const i=this.createTerminalHandler(new AcpTransport(t=>{if(this.client===e&&e.readyState===WebSocket.OPEN)try{e.send(JSON.stringify(t))}catch(e){this.logger.debug?.("[acp-server] connection terminal send failed",e)}},this.writeObs));this.acpConnectionTerminalHandlers.set(e,i),this.core.initialized=!1,this.logger.info?.("[acp-server] ACP client connected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"client.connected",severity:"info"}),e.on("message",t=>{if(this.client!==e)return;const n="string"==typeof t?t:t.toString("utf-8");try{const e=JSON.parse(n);if("id"in e&&void 0!==e.id){(e.method.startsWith("terminal/")?i.handleRequest(e):this.core.handleRequest(e)).catch(e=>{this.logger.error?.(`[acp-server] request failed: ${e instanceof Error?e.message:String(e)}`)})}else e.method.startsWith("terminal/")?i.handleNotification(e.method,e.params):this.core.handleNotification(e)}catch{this.logger.error?.("[acp-server] Invalid JSON-RPC message")}}),e.on("close",()=>{i.destroy(),this.acpConnectionTerminalHandlers.delete(e),this.client===e&&(this.client=null,this.core.initialized=!1),this.logger.info?.("[acp-server] ACP client disconnected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"client.disconnected",severity:"info"})})}createTerminalHandler(e){const t=new TerminalBridgeHandler({terminalMgr:this.terminalMgr,transport:e,config:this.config,logger:this.logger,writeObs:this.writeObs,localAuthority:this.localTerminalAuthorityRegistry.forPrincipal(this.localTerminalAuthorityRegistry.issuePrincipal()),executeCanonical:(e,t,i,n)=>this.core.dispatchLegacyTerminalCapability(e,t,i,n)});return t.setupListeners(),t}setCanvasState(e,t){this.canvasOrch.setCanvasState(e,t)}wireRelayActionHandler(){this.canvasOrch.wireRelayActionHandler()}isCanvasActive(e){return this.canvasOrch.isCanvasActive(e)}isCanvasActiveForGatewayKey(e){return this.canvasOrch.isCanvasActiveForGatewayKey(e)}broadcastNotification(e,t){this.transport.sendNotification(e,t)}getActiveSessionId(){return this.state.lastActiveSessionId}getSessionIdForToolCall(e){const t=this.pendingToolCallSessionKeys.length>0?this.pendingToolCallSessionKeys[this.pendingToolCallSessionKeys.length-1]:void 0;if(t){const i=this.state.findSessionByKey(t)??this.core.ensureSessionForKey(t);if(i?.sessionId)return e&&this.state.toolCallToSessionMap.set(e,i.sessionId),i.sessionId}if(e){const t=this.state.toolCallToSessionMap.get(e);if(t)return t;const i=this.state.toolCallIdMap.get(e);if(i&&i!==e){const e=this.state.toolCallToSessionMap.get(i);if(e)return e}const n=e.split("_");if(n.length>=3&&"tc"===n[0]){const e=n[1],t=this.state.promptsByRunId.get(e);if(t?.sessionId)return t.sessionId}}this.logger.warn?.(`[acp-server] getSessionIdForToolCall MISS: toolCallId=${e??"(none)"}, sessions=${this.state.sessions.size}, pendingKey=${t??"none"}`)}getSessionIdForActivePrompt(){return this.getSessionIdForToolCall()}setPendingToolCallSessionKey(e){e&&(this.pendingToolCallSessionKeys.push(e),this.pendingToolCallSessionKeys.length>16&&this.pendingToolCallSessionKeys.shift())}preMapToolCallSession(e){const t=this.pendingToolCallSessionKeys.length>0?this.pendingToolCallSessionKeys[this.pendingToolCallSessionKeys.length-1]:void 0;if(!t||!e)return;if(this.state.toolCallToSessionMap.has(e))return;const i=this.state.findSessionByKey(t)??this.core.ensureSessionForKey(t);i?.sessionId&&this.state.toolCallToSessionMap.set(e,i.sessionId)}waitForAgentMapping(){return this.core.waitForAgentMapping()}onceAgentMappingReady(e){this.core.onceAgentMappingReady(e)}onAgentMappingUpdated(e){this.core.onAgentMappingUpdated(e)}flushPendingCanvasLinks(){this.canvasOrch.flushPendingCanvasLinks()}onRelayReconnected(){this.canvasOrch.onRelayReconnected()}getLocalAgentId(){return this.config.gateway.agentId||this.core.getDefaultAgentId()}resolveLocalAgentIdForPlatformAgent(e){return this.core.resolveLocalAgentId(e)}getDefaultPlatformAgentId(){return this.getDefaultPlatformAgent()?.id}getDefaultPlatformAgent(){const e=this.core.agentMapping;if(e&&0!==e.size)return e.values().next().value??void 0}getPlatformAgentMappings(){const e=this.core.agentMapping;return e&&0!==e.size?[...e.entries()].map(([e,t])=>({localAgentId:e,platformAgent:t})):[]}getPlatformAgentIdForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const i=this.state.sessions.get(t);return this.core.resolvePlatformAgentId(i?.agentId??"")??i?.platformAgentId}getGatewaySessionKeyForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const i=this.state.sessions.get(t);return i?.gatewaySessionKey}getMentionsForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const i=this.state.sessions.get(t);return i?.activeMentions}getResourceRenderer(){return this.canvasOrch.resourceRenderer}getDescriptorRegistry(){return this.canvasOrch.descriptorRegistry}getFormBuilder(){return this.canvasOrch.formBuilder}getSchemaRegistry(){return this.canvasOrch.schemaRegistry}registerSkillFormSchemas(e,t){const i=this.canvasOrch.schemaRegistry;i&&import("node:fs/promises").then(async({readFile:n})=>{try{const r=`${t}/skill.json`,s=await n(r,"utf-8"),a=JSON.parse(s);if(a.formSchemas?.length){for(const e of a.formSchemas)i.register({...e,submitLabel:e.submitLabel||"Submit",fields:e.fields});this.logger.info?.(`[acp-server] Registered ${a.formSchemas.length} form schema(s) from skill "${e}"`)}}catch{}}).catch(e=>{this.logger.debug?.(`[acp-server] skill form schema import failed: ${e instanceof Error?e.message:String(e)}`)})}emitCanvasActionAsUserMessage(e,t){this.canvasOrch.emitCanvasActionAsUserMessage(e,t)}stop(){const e=this.teardownContext.getStore();if(e?.server===this){const t=e.generation;return this.activeGeneration!==t?t.stopPromise??t.teardownPromise??Promise.resolve():(e.detachOwner(),t.stopPromise??Promise.resolve())}const t=this.activeGeneration;if(!t||"stopped"===this.lifecyclePhase)return Promise.resolve();if("stopping"===this.lifecyclePhase)return t.stopPromise??Promise.resolve();let i,n;const r=new Promise((e,t)=>{i=e,n=t});t.stopPromise=r,this.lifecyclePhase="stopping",t.abortController.abort();const s=this.teardownGeneration(t);return(async()=>{try{t.startPromise.catch(()=>{}),await s,i()}catch(e){n(e)}finally{this.activeGeneration===t&&(this.activeGeneration=void 0,this.lifecyclePhase="stopped")}})(),r}teardownGeneration(e){return e.teardownPromise||(e.teardownPromise=this.teardownGenerationOnce(e)),e.teardownPromise}async teardownGenerationOnce(e){const t=[],i=new AbortController;let n,r=!1;const s=new Promise(e=>{n=setTimeout(()=>{r=!0,i.abort(),e()},ACPServer.LIFECYCLE_TEARDOWN_TIMEOUT_MS),n.unref?.()}),a=async(n,r)=>{let a;const o=new Promise(e=>{a=e}),c=Promise.resolve().then(()=>this.teardownContext.run({server:this,generation:e,detachOwner:a},()=>r(i.signal))),l=await Promise.race([c.then(()=>({status:"completed"}),e=>({status:"failed",error:e})),s.then(()=>({status:"timed_out"})),o.then(()=>({status:"detached"}))]);if("failed"===l.status)t.push(l.error);else if("timed_out"===l.status){const i=new Error(`lifecycle teardown timed out: ${n}`);t.push(i),this.logger.error?.(`[acp-server] ${i.message} generation=${e.id}`),this.writeObs({component:"acp-server",domain:"lifecycle",name:"lifecycle.teardown_timeout",severity:"error",payload:{owner:n,generation:e.id,timeoutMs:ACPServer.LIFECYCLE_TEARDOWN_TIMEOUT_MS}})}},o=[];this.setTerminalServiceReady(e,!1),o.push(a("session-mutation-coordinator",()=>this.sessionMutationCoordinator.fenceActive("gateway_disconnected"))),this.agentSyncRetryTimer&&(clearTimeout(this.agentSyncRetryTimer),this.agentSyncRetryTimer=null),e.projectionStarted&&(e.projectionStarted=!1,o.push(a("agent-projection",()=>this.agentProjection?.stop()))),o.push(a("session-state",()=>this.state.destroy())),o.push(a("canvas-orchestrator",()=>this.canvasOrch.destroy())),o.push(a("mcp-receipt-history",()=>this.core.setRuntimeReceiptHistoryPort(void 0)));const c=e.gateway,l=e.bridge,h=e.bridgeSocket,g=e.terminalOutbound,d=e.runtimeConsumer,u=this.client&&this.client!==h?this.client:null,m=this.wss;if(e.gateway=void 0,e.bridge=void 0,e.bridgeSocket=void 0,e.terminalOutbound=void 0,e.runtimeConsumer=void 0,this.gateway===c&&(this.gateway=null),this.bridgeInstance===l&&(this.bridgeInstance=null),this.client!==u&&this.client!==h||(this.client=null),this.terminalOutbound===g&&(this.terminalOutbound=null),this.runtimeRpcConsumer===d&&(this.runtimeRpcConsumer=void 0),this.wss=null,this.bridgeConsecutiveFailures=0,c&&o.push(a("gateway",async()=>{try{await c.stop()}finally{c.removeAllListeners()}})),u&&o.push(a("incoming-client",()=>{u.removeAllListeners(),u.close()})),l&&o.push(a("bridge",async()=>{try{await this.closeBridgeOnce(l)}catch(e){this.logger.debug?.(`[acp-server] bridge.close failed during stop: ${e instanceof Error?e.message:String(e)}`)}})),g&&o.push(a("terminal-outbound",e=>g.drain({timeoutMs:ACPServer.LIFECYCLE_TEARDOWN_TIMEOUT_MS,terminateAfterTimeout:!0,signal:e}))),e.authorityOwned&&(e.authorityOwned=!1,o.push(a("runtime-authority",()=>e.authority?.stop()))),d&&o.push(a("runtime-consumer",e=>this.closeConsumerOnce(d,e))),m&&o.push(a("websocket-server",()=>{m.removeAllListeners(),m.close()})),await Promise.all(o),await a("runtime-authority-reset",()=>e.authority?.setRuntimeConsumerLive(!1)),await a("terminal-handler",()=>this.terminalHandler.destroy()),await a("terminal-manager",()=>this.terminalMgr.destroy()),n&&clearTimeout(n),r||i.abort(),this.logger.info?.("[acp-server] Stopped"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"lifecycle.shutdown",severity:"info"}),t.length>0)throw t[0]}}
|
|
1
|
+
import{WebSocketServer,WebSocket}from"ws";import{randomUUID}from"node:crypto";import{AsyncLocalStorage}from"node:async_hooks";let BridgeClientCtor;import{GatewayClient}from"./gateway-client.js";import{TerminalManager}from"./terminal/session-manager.js";import{getOrCreateRuntimeTerminalService}from"./terminal/runtime-terminal-service.js";import{RuntimeTerminalOutboundRegistration}from"./terminal/runtime-terminal-outbound-registration.js";import{FileTerminalAuthorityJournal}from"./terminal/terminal-authority-journal.js";import{LocalTerminalAuthorityRegistry}from"./terminal/local-terminal-authority.js";import{TerminalLeaseBoundBridgeClient}from"./terminal/terminal-lease-bound-bridge-client.js";import{AgentGatewayHttpProjectionTransport,AgentGatewayTemplateOriginReporter,AgentProjectionProduction,OpenClawAgentProjectionSource}from"./runtime/agent-projection-production.js";import{join}from"node:path";import{AcpTransport}from"./acp-gateway/transport.js";import{AcpGatewaySessionState}from"./acp-gateway/session-state.js";import{AcpGatewayBridgeCore}from"./acp-gateway/bridge-core.js";import{BeeosFileResolver}from"./acp-gateway/beeos-file-resolver.js";import{AcpGatewayEvents}from"./acp-gateway/gateway-events.js";import{sweepStaleSessionLocks}from"./acp-gateway/stale-session-lock-sweeper.js";import{TerminalBridgeHandler}from"./terminal/bridge-handler.js";import{isAgentSyncWaitForGateway,syncAgentsToPlatform}from"./agent-sync.js";import{CanvasOrchestrator}from"./canvas-orchestrator.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{writeDiscoveryCache}from"./discovery-cache.js";function formatBridgeEventArgs(e){return 0===e.length?"(no args)":e.map((e,t)=>`arg${t}=${formatBridgeEventValue(e)}`).join(" ")}function formatBridgeEventValue(e){if(void 0===e)return"undefined";if(null===e)return"null";if(e instanceof Error){const t=e.stack?e.stack.split("\n").slice(0,4).join(" | "):"";return`Error(name=${e.name},message=${e.message}${t?`,stack=${t}`:""})`}if("object"==typeof e)try{return JSON.stringify(e,jsonReplacerForLogs)}catch{return String(e)}return String(e)}function jsonReplacerForLogs(e,t){return t instanceof Error?{name:t.name,message:t.message,stack:t.stack}:t}export class ACPServer{agentFetchFn;instanceId;runtimeAuthority;mcpToolBridge;mcpCredentialReferences;boundAgentFetchFn;wss=null;gateway=null;client=null;config;logger;writeObs;transport;state;core;events;terminalMgr;terminalHandler;localTerminalAuthorityRegistry;acpConnectionTerminalHandlers=new WeakMap;runtimeTerminalService;terminalOutbound=null;terminalServiceReady=!1;runtimeAgentCapabilities;agentProjection;runtimeRpcConsumer;runtimeRpcConsumerFactory;canvasOrch;sessionMutationCoordinator;messageClient;msgPrincipalId=null;pendingToolCallSessionKeys=[];thirdPartyChannels;agentSyncRetryTimer=null;agentSyncInFlight=!1;runtimeServiceFactoryFenced=!1;legacyFencedSyncFailureCount=0;lifecyclePhase="stopped";lifecycleGeneration=0;activeGeneration;bridgeInstance=null;bridgeClosePromises=new WeakMap;consumerClosePromises=new WeakMap;teardownContext=new AsyncLocalStorage;bridgeConsecutiveFailures=0;static RE_DISCOVER_THRESHOLD=2;static LIFECYCLE_CONNECT_TIMEOUT_MS=1e4;static LIFECYCLE_TEARDOWN_TIMEOUT_MS=5e3;drainNextDeferredSend(e){const t=e.pendingAgentSends?.shift();t&&(e.pendingAgentSends?.length||(e.pendingAgentSends=void 0),this.agentSend(t.message,t.attachments,e.gatewaySessionKey).catch(e=>this.logger.warn?.("[acp-server] Deferred agentSend failed: %s",e)))}async gatewayRequest(e,t){if(!this.gateway?.isConnected)throw new Error("Gateway not connected");return this.gateway.request(e,t)}async chatAbort(e,t){const i=this.gateway;if(!i?.isConnected)throw new Error("Gateway not connected");return i.chatAbort(e,t)}async agentSend(e,t,i){if(!this.gateway?.isConnected)throw new Error("Gateway not connected");const n=this.config.gateway.agentId||this.core.getDefaultAgentId();let r,s;i?(r=this.state.findSessionByKey(i),s=i,r?this.logger.info?.(`[acp-server] agentSend: resolved session for key=${i} sid=${r.sessionId}`):(r=this.core.ensureSessionForKey(s),this.logger.info?.(`[acp-server] agentSend: created session for key=${i} sid=${r.sessionId}`))):(s=`agent:${n}:ephemeral:${randomUUID()}`,r=this.core.ensureSessionForKey(s),this.logger.info?.(`[acp-server] agentSend: no targetSessionKey, created ephemeral session key=${s}`));const a=await this.sessionMutationCoordinator.acquireInternal({sessionKey:s,label:"agentSend"});let o=!1;try{const i=this.gateway;if(!i?.isConnected)throw new Error("Gateway not connected");if(r&&!r.promptDone&&(void 0!==r.pendingPromptId||void 0!==r.currentRunId||void 0!==r.gatewayRequestId))return(r.pendingAgentSends??=[]).push({message:e,attachments:t}),this.logger.info?.("[acp-server] Session has active run, deferred message (queue=%d)",r.pendingAgentSends.length),void this.sessionMutationCoordinator.releaseBeforeRuntime(a);o=!0;const c=await i.agentSend(s,e,n,t);return c?.runId&&r?(this.state.resetForNewRun(r,r.sessionId,this.transport),r.currentRunId=c.runId,this.sessionMutationCoordinator.bindRun(a,c.runId),this.core.observeActiveRun(r),r.assistantStreamStartedAt=Date.now(),r.writeGateToken=a,this.state.promptsByRunId.set(c.runId,r)):this.sessionMutationCoordinator.releaseBeforeRuntime(a),c}catch(e){throw o?this.sessionMutationCoordinator.requireRecovery(a,"terminal_not_observed"):this.sessionMutationCoordinator.releaseBeforeRuntime(a),e}}setMessageClient(e,t){if(this.messageClient&&this.messageClient!==e)throw new Error("AcpServer already owns a different MessageClient");this.messageClient=e,this.msgPrincipalId=t,this.logger.info?.(`[acp-server] MessageEnvelope v3 streamer ready (principal=${t})`)}getMessageClient(){if(!this.messageClient)throw new Error("MessageClient has not been composed");return this.messageClient}getRuntimeCommandDispatcher(){return this.core.getRuntimeCommandDispatcher()}getActiveRunAuthority(){return this.core.getActiveRunAuthority()}ensureImInboundSession(e,t,i){const n=this.core.ensureSessionForKey(e);n.channelId=t,n.inboundMessageId=i}sendBridgeMessage=e=>{const t=this.client;if(t&&t.readyState===WebSocket.OPEN)try{t.send(JSON.stringify(e))}catch(e){this.logger.debug?.("[acp-server] bridge send failed (socket may be closing):",e)}else this.logger.debug?.("[acp-server] bridge not ready, dropping message")};constructor(e,t,i,n,r,s,a,o,c,l,h,g,d){this.agentFetchFn=r,this.instanceId=s,this.runtimeAuthority=c,this.mcpToolBridge=h,this.mcpCredentialReferences=g,this.boundAgentFetchFn=d,this.config=e,this.logger=t,this.writeObs=n??(()=>{}),this.thirdPartyChannels=a??new Set,this.sessionMutationCoordinator=i,this.runtimeRpcConsumerFactory=l,this.runtimeAgentCapabilities={canvas:this.config.canvasEnabled,terminal:!1,webSsh:!1,fileSystem:!0,image:!0,audio:!1,mcp:!0,cron:!0,streaming:!0,pushNotifications:this.config.messageBus.enabled},this.runtimeAuthority?.setRuntimeRealtimeCapabilityReady("canvas",this.config.canvasEnabled),this.runtimeAuthority?.setRuntimeRealtimeCapabilityReady("terminal",!0===this.config.terminalServiceEnabled),this.transport=new AcpTransport(this.sendBridgeMessage,this.writeObs,void 0,()=>{const e=this.client;return!!e&&e.readyState===WebSocket.OPEN}),this.state=new AcpGatewaySessionState(this.logger);const u=this.agentFetchFn&&this.config.agentGatewayUrl?new BeeosFileResolver({agentGatewayUrl:this.config.agentGatewayUrl,agentFetch:this.agentFetchFn,writeObs:this.writeObs}):void 0,m=this.runtimeAuthority&&this.config.agentGatewayUrl?new AgentGatewayTemplateOriginReporter({agentGatewayUrl:this.config.agentGatewayUrl,boundAgentFetch:this.runtimeAuthority.boundAgentFetch}):void 0;this.core=new AcpGatewayBridgeCore({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,getGateway:()=>this.gateway,instanceId:this.instanceId,getAuthoritativeInstanceId:()=>process.env.BEEOS_INSTANCE_ID?.trim()||this.getDefaultPlatformAgent()?.instanceId,thirdPartyChannels:this.thirdPartyChannels,beeosFileResolver:u,agentFetch:this.agentFetchFn,agentGatewayUrl:this.config.agentGatewayUrl,sessionMutationCoordinator:i,triggerAgentSync:()=>this.tryAgentSync(),runtimeAgentIdentities:this.runtimeAuthority?.identities,runtimeCommandJournal:this.runtimeAuthority?.journal,markAgentProjectionDirty:this.runtimeAuthority?()=>this.agentProjection?.markDirty():void 0,getAgentProjection:this.runtimeAuthority?()=>this.agentProjection:void 0,templateOriginReporter:m,mcpToolBridge:this.mcpToolBridge,mcpCredentialReferences:this.mcpCredentialReferences}),this.runtimeAuthority&&this.agentFetchFn&&this.config.agentGatewayUrl&&(this.agentProjection=new AgentProjectionProduction({authority:this.runtimeAuthority,stateFile:join(this.runtimeAuthority.stateDirectory,"runtime-agent-projection.json"),journal:this.runtimeAuthority.journal,identities:this.runtimeAuthority.identities,source:new OpenClawAgentProjectionSource(()=>this.gateway),transport:new AgentGatewayHttpProjectionTransport({agentGatewayUrl:this.config.agentGatewayUrl,boundAgentFetch:this.runtimeAuthority.boundAgentFetch,getRuntimeLeaseCredential:e=>{const t=this.runtimeAuthority?.authorityLease;if(!t||t.instanceId!==e.instanceId||t.leaseId!==e.leaseId||t.runtimeEpoch!==e.runtimeEpoch||t.handlerIdentity!==e.handlerIdentity)throw new Error("agent projection fenced by current runtime lease");return t.runtimeLeaseCredential},onAccepted:e=>{e.size>0&&this.core.setAgentMapping(e)}}),capabilities:this.runtimeAgentCapabilities,logger:this.logger})),this.canvasOrch=new CanvasOrchestrator({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,getSessionIdForToolCall:e=>this.getSessionIdForToolCall(e),gateway:null,getDefaultAgentId:()=>this.core.getDefaultAgentId(),resolvePlatformAgentId:e=>this.core.resolvePlatformAgentId(e)??""}),this.events=new AcpGatewayEvents({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,completePrompt:(e,t)=>this.core.completePrompt(e,t),failPrompt:(e,t,i)=>this.core.failPrompt(e,t,i),interruptPrompt:(e,t)=>this.core.interruptPrompt(e,t),schedulePromptTimeout:e=>this.core.schedulePromptTimeout(e),adoptBackgroundMutation:(e,t)=>this.core.adoptBackgroundMutation(e,t),abortGatewayRun:async(e,t)=>{await this.chatAbort(e,t)},maybeCompletePromptFromChatState:(e,t)=>this.core.maybeCompletePromptFromChatState(e,t),agentFetch:this.agentFetchFn,getMessageClient:()=>this.messageClient??null,getMsgPrincipalId:()=>this.msgPrincipalId??null,getCanvasState:()=>this.canvasOrch.canvasState,gatewayRequest:(e,t)=>{const i=this.gateway;return i?.isConnected?i.request(e,t):Promise.reject(new Error("gateway not connected"))},cronChannelCache:this.core.cronChannelCache}),this.core.onRunEnded=(e,t)=>{"complete"===t.kind?this.canvasOrch.onPromptComplete(e):this.canvasOrch.onPromptFailed(),this.drainNextDeferredSend(e)},this.terminalMgr=o??new TerminalManager,this.localTerminalAuthorityRegistry=new LocalTerminalAuthorityRegistry(this.terminalMgr),this.terminalHandler=this.createTerminalHandler(this.transport),this.runtimeTerminalService=getOrCreateRuntimeTerminalService(this.terminalMgr),this.core.setRuntimeTerminalService(this.runtimeTerminalService),this.core.handleTerminalRequest=e=>this.terminalHandler.handleRequest(e),this.core.handleTerminalNotification=(e,t)=>this.terminalHandler.handleNotification(e,t),this.core.handleCanvasNotification=(e,t)=>{this.canvasOrch.handleCanvasNotification(e,t)},this.core.getCanvasState=()=>this.canvasOrch.canvasState,this.core.onSessionLoaded=(e,t,i)=>{this.canvasOrch.onSessionLoaded(e,t,i)},this.core.onSkillInstalled=(e,t)=>{this.registerSkillFormSchemas(e,t)},this.core.onSessionDeleted=e=>{this.canvasOrch.onSessionDeleted(e)},this.core.onSessionCleared=(e,t)=>{this.canvasOrch.onSessionCleared(e,t)},this.core.handleSlashCommand=async e=>this.canvasOrch.handleSlashCommand(e),this.core.onPromptStart=()=>{this.canvasOrch.onPromptStart()},this.state.startSessionGc(e=>{e.writeGateToken&&this.sessionMutationCoordinator.requireRecovery(e.writeGateToken,"terminal_not_observed"),this.core.cleanupPrompt(e)},e=>{this.canvasOrch.canvasState?.removeSession(e.gatewaySessionKey),this.transport.resetSessionUpdateCount(e.sessionId)})}setRuntimeRpcConsumerFactory(e){if(e!==this.runtimeRpcConsumerFactory){if("stopped"!==this.lifecyclePhase)throw new Error("runtime Service factory is immutable after ACP start");this.runtimeRpcConsumerFactory=e}}start(e){const t=this.activeGeneration;if("starting"===this.lifecyclePhase||"started"===this.lifecyclePhase)return this.logger.info?.("[acp-server] start() skipped — already started (singleton reuse)"),t?.startPromise??Promise.resolve();if("stopping"===this.lifecyclePhase){return(t?.stopPromise??Promise.resolve()).then(()=>this.start(e),()=>this.start(e))}const i={id:++this.lifecycleGeneration,factory:this.runtimeRpcConsumerFactory,authority:this.runtimeAuthority,abortController:new AbortController,startPromise:Promise.resolve(),authorityOwned:!1,projectionStarted:!1};return this.lifecyclePhase="starting",this.activeGeneration=i,i.startPromise=this.startGeneration(i,e).then(()=>{this.activeGeneration===i&&"starting"===this.lifecyclePhase&&(this.lifecyclePhase="started")}).catch(async e=>{try{await this.teardownGeneration(i)}catch(e){this.logger.debug?.(`[acp-server] failed-start teardown also failed: ${e instanceof Error?e.message:String(e)}`)}finally{this.activeGeneration===i&&"starting"===this.lifecyclePhase&&(this.activeGeneration=void 0,this.lifecyclePhase="stopped")}throw e}),i.startPromise}async startGeneration(e,t){const i=e.authority,n=e.factory;let r=!1;if(i&&n){e.authorityOwned=!0;const t=await this.waitForGeneration(i.prepareConsumerBinding(),e);if("completed"!==t.status||!this.shouldContinueStart(e))return;const s=n.create({dispatcher:this.core.getRuntimeCommandDispatcher(),binding:t.value,getBinding:()=>i.currentConsumerBinding()??t.value,getLease:()=>i.authorityLease,signal:e.abortController.signal}),a=await this.waitForGeneration(s,e);if("completed"!==a.status)return void("cancelled"===a.status&&s.then(e=>this.closeConsumerOnce(e),()=>{}));const o=a.value;if(!this.shouldContinueStart(e))return void this.closeConsumerOnce(o,e.abortController.signal).catch(()=>{});if(this.core.setRuntimeReceiptHistoryPort(o.receiptHistory),e.runtimeConsumer=o,this.runtimeRpcConsumer=o,o.start(),!this.shouldContinueStart(e))return;i.setRuntimeConsumerLive(!0),r=!0,this.runtimeServiceFactoryFenced=!1}else i&&(e.authorityOwned=!0),i?.setRuntimeConsumerLive(!1),i&&(this.runtimeServiceFactoryFenced=!0,this.logger.warn?.("[acp-server] runtime Service factory unavailable — registration fenced"));if(this.core.startMcp().catch(e=>{this.logger.warn?.(`[acp-server] MCP desired-state reconcile failed: ${e instanceof Error?e.message:String(e)}`)}),!this.shouldContinueStart(e))return;if(i&&r){if("completed"!==(await this.waitForGeneration(i.start(e.abortController.signal),e)).status||!this.shouldContinueStart(e))return}this.agentProjection&&(e.projectionStarted=!0,this.agentProjection.start());const s=Date.now();this.logger.info?.("[acp-server] start() begin"),t&&(this.config.bridge.url=t);const a=this.config.bridge.url,o=this.config.keyFile;if(a&&!BridgeClientCtor)try{const t=Date.now(),i=await this.waitForGeneration(import("@beeos-ai/bridge-client"),e);if("completed"!==i.status)return;BridgeClientCtor=i.value.BridgeClient,this.logger.info?.(`[acp-server] bridge-client import took ${Date.now()-t}ms`)}catch(e){this.logger.warn?.("[acp-server] Bridge SDK not available")}if(!this.shouldContinueStart(e))return;if(!0===this.config.terminalServiceEnabled&&a&&i){const t=new TerminalLeaseBoundBridgeClient({bridgeUrl:a,authority:i,keys:i.agentKeys}),n=new RuntimeTerminalOutboundRegistration({client:t,manager:this.terminalMgr,service:this.runtimeTerminalService,authorityJournal:new FileTerminalAuthorityJournal(join(i.stateDirectory,"terminal-authority-journal.json")),onAvailabilityChange:t=>this.setTerminalServiceReady(e,t),logger:this.logger});if(!this.shouldContinueStart(e))return void n.drain({timeoutMs:5e3,terminateAfterTimeout:!0,signal:e.abortController.signal}).catch(()=>{});e.terminalOutbound=n,this.terminalOutbound=n;try{const t=await this.waitForGeneration(n.start(),e,ACPServer.LIFECYCLE_CONNECT_TIMEOUT_MS);if("cancelled"===t.status)return;if("timed_out"===t.status)throw new Error("Terminal connection timed out")}catch(e){this.logger.warn?.(`[terminal-service] initial outbound registration failed; no legacy fallback: ${e instanceof Error?e.message:String(e)}`)}if(!this.shouldContinueStart(e))return}else!0!==this.config.terminalServiceEnabled||this.runtimeAuthority||this.logger.warn?.("[terminal-service] disabled: active Runtime registration authority unavailable");const c=Date.now();if(await this.connectGateway(e),this.shouldContinueStart(e)){this.logger.info?.(`[acp-server] connectGateway() took ${Date.now()-c}ms`);try{const t=await this.waitForGeneration(sweepStaleSessionLocks({logger:this.logger}),e);if("completed"!==t.status)return;const i=t.value;this.logger.info?.(`[acp-server] stale lock sweep scanned=${i.scanned} quarantined=${i.quarantined} skipped=${i.skipped}`)}catch(e){this.logger.warn?.(`[acp-server] stale lock sweep failed: ${e instanceof Error?e.message:String(e)}`)}if(this.shouldContinueStart(e)){if(a&&o&&BridgeClientCtor){Date.now();await this.startBridgeMode(a,o,e)}else this.logger.warn?.("[acp-server] Bridge not configured; running in standby (no ACP listener). Use handleUpgrade() for noServer mode."),this.core.markAgentSyncDone();this.logger.info?.(`[acp-server] start() total ${Date.now()-s}ms`)}}}shouldContinueStart(e){return!e.abortController.signal.aborted&&this.activeGeneration===e&&"starting"===this.lifecyclePhase}isGenerationActive(e){return!e.abortController.signal.aborted&&this.activeGeneration===e&&("starting"===this.lifecyclePhase||"started"===this.lifecyclePhase)}setTerminalServiceReady(e,t){if(this.activeGeneration!==e)return;const i=!0===this.config.terminalServiceEnabled&&t;this.terminalServiceReady!==i&&(this.terminalServiceReady=i,this.runtimeAgentCapabilities.terminal=i,this.agentProjection?this.agentProjection.markDirty():this.tryAgentSync())}async waitForGeneration(e,t,i){const n=t.abortController.signal;return n.aborted?(e.catch(()=>{}),{status:"cancelled"}):await new Promise((t,r)=>{let s,a=!1;const o=e=>{a||(a=!0,n.removeEventListener("abort",c),s&&clearTimeout(s),t(e))},c=()=>o({status:"cancelled"});n.addEventListener("abort",c,{once:!0}),void 0!==i&&(s=setTimeout(()=>o({status:"timed_out"}),i),s.unref?.()),e.then(e=>o({status:"completed",value:e}),e=>{a||(a=!0,n.removeEventListener("abort",c),s&&clearTimeout(s),r(e))})})}closeConsumerOnce(e,t){const i=this.consumerClosePromises.get(e);if(i)return i;const n=Promise.resolve().then(()=>e.stop(t));return this.consumerClosePromises.set(e,n),n}async startBridgeMode(e,t,i){if(!BridgeClientCtor)throw new Error("@beeos-ai/bridge-client not available");if(!this.isGenerationActive(i))return;const n=new BridgeClientCtor({bridgeUrl:e,service:this.config.bridge.service||"acp",keyFile:t});if(!this.isGenerationActive(i))return void this.closeBridgeOnce(n,!1).catch(()=>{});i.bridge=n,this.bridgeInstance=n;const r=()=>this.bridgeInstance===n;n.on("connected",()=>{r()&&(this.terminalHandler.destroy(),this.terminalHandler=this.createTerminalHandler(this.transport),this.bridgeConsecutiveFailures=0,this.logger.info?.("[acp-server] Connected to Bridge (publicKey auth)"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"bridge.connected",severity:"info"}),this.tryAgentSync())}),n.on("disconnected",(...e)=>{if(!r())return;this.terminalHandler.destroy();const s=String(e[0]??"unknown"),a=formatBridgeEventArgs(e);if(this.logger.warn?.(`[acp-server] Bridge disconnected: ${s} (detail=${a})`),this.core.initialized=!1,this.bridgeConsecutiveFailures++,this.bridgeConsecutiveFailures>=ACPServer.RE_DISCOVER_THRESHOLD&&this.agentFetchFn&&this.config.agentGatewayUrl){this.bridgeConsecutiveFailures=0;const e=this.config.bridge.url;discoverBridgeFromGW(this.config.agentGatewayUrl,this.agentFetchFn).then(async s=>{if(this.isGenerationActive(i)&&r()&&s&&s.url!==e){if(this.logger.info?.(`[acp-server] Bridge re-discovery: URL changed ${e} → ${s.url}, switching`),this.config.bridge.url=s.url,writeDiscoveryCache({bridge:{url:s.url,discoveredAt:Date.now()}}),await this.closeBridgeOnce(n,!1),!this.isGenerationActive(i)||this.bridgeInstance!==n)return;i.bridge===n&&(i.bridge=void 0),this.activeGeneration!==i||"starting"!==this.lifecyclePhase&&"started"!==this.lifecyclePhase||await this.startBridgeMode(s.url,t,i)}}).catch(()=>{})}}),n.on("message",(...e)=>{if(!r())return;const t=e[0],i="string"==typeof t?t:Buffer.from(t).toString("utf-8");try{const e=JSON.parse(i);"id"in e&&void 0!==e.id?this.core.handleRequest(e).catch(e=>{this.logger.error?.(`[acp-server] Bridge request failed: ${e instanceof Error?e.message:String(e)}`)}):this.core.handleNotification(e)}catch{this.logger.error?.("[acp-server] Invalid JSON-RPC message from Bridge")}}),n.on("error",(...e)=>{if(!r())return;const t=formatBridgeEventArgs(e);this.logger.error?.(`[acp-server] Bridge error: ${t}`)});const s={readyState:WebSocket.OPEN,send:(e,t)=>{n.send(e).then(()=>t?.(),e=>{const i=e instanceof Error?e:new Error(String(e));this.logger.warn?.(`[acp-server] bridge.send failed: ${i.message}`),t?.(i)})},close:()=>{this.closeBridgeOnce(n).catch(e=>{this.logger.debug?.(`[acp-server] bridge.close failed: ${e instanceof Error?e.message:String(e)}`)})},terminate:()=>{this.closeBridgeOnce(n,!1).catch(e=>{this.logger.debug?.(`[acp-server] bridge.terminate failed: ${e instanceof Error?e.message:String(e)}`)})},on:()=>s,once:()=>s,removeListener:()=>s,removeAllListeners:()=>s,ping:()=>{}};Object.defineProperty(s,"readyState",{get:()=>"connected"===n.state?WebSocket.OPEN:WebSocket.CLOSED}),i.bridgeSocket=s,this.terminalHandler.destroy(),this.terminalHandler=this.createTerminalHandler(this.transport),this.client=s;const a=Date.now();try{const e=await this.waitForGeneration(n.connect(),i,ACPServer.LIFECYCLE_CONNECT_TIMEOUT_MS);if("cancelled"===e.status)return;if("timed_out"===e.status)throw new Error("Bridge connection timed out");this.logger.info?.(`[acp-server] bridge.connect() took ${Date.now()-a}ms`)}catch(e){this.logger.warn?.(`[acp-server] Initial bridge connect failed after ${Date.now()-a}ms (will retry in background): ${e?.message}`)}this.core.initialized=!1,this.logger.info?.("[acp-server] Running in Bridge mode (WS client)")}closeBridgeOnce(e,t){const i=this.bridgeClosePromises.get(e);if(i)return i;const n=Promise.resolve().then(()=>e.close(t));return this.bridgeClosePromises.set(e,n),n}handleUpgrade(e,t,i){this.wss||(this.wss=new WebSocketServer({noServer:!0}),this.wss.on("connection",(e,t)=>this.handleConnection(e,t))),this.wss.handleUpgrade(e,t,i,t=>{this.wss.emit("connection",t,e)})}async tryAgentSync(e=0){if(this.agentSyncRetryTimer&&(clearTimeout(this.agentSyncRetryTimer),this.agentSyncRetryTimer=null),!this.client||this.client.readyState!==WebSocket.OPEN)return;if(!this.gateway||!this.agentFetchFn||!this.config.agentGatewayUrl)return void this.core.markAgentSyncDone();if(this.agentProjection){if(this.runtimeAuthority?.authorityLease)return void this.agentProjection.onGatewayReconnect();if(!this.runtimeServiceFactoryFenced)return;0===e&&this.logger.error?.("[acp-server] runtime Service factory fenced with no authority lease — attempting one-shot legacy agent-sync bootstrap to recover Agent Identity")}const t=!!this.agentProjection;if(t||(this.legacyFencedSyncFailureCount=0),this.agentSyncInFlight)return;const i=[1e3,2e3,5e3,1e4],n=3e5,r=()=>{const r=e+1;if(t&&(this.legacyFencedSyncFailureCount+=1,this.legacyFencedSyncFailureCount>=4)){const e=Math.round(5);return this.logger.error?.(`[acp-server] legacy agent-sync bootstrap fallback has failed ${this.legacyFencedSyncFailureCount} consecutive times while the runtime Service factory is fenced — this fallback path is confirmed incompatible with a backend that requires the runtime-projection agent-sync schema and cannot self-heal; restart this instance once Message Service / the runtime Service factory recover. Backing off to a ${e}m retry interval to stop busy-looping a request that cannot succeed.`),void(this.agentSyncRetryTimer=setTimeout(()=>{this.tryAgentSync(r)},n))}const s=i[Math.min(e,i.length-1)],a=Math.round(s/1e3),o=`[acp-server] agent sync retry #${r} in ${a}s`;r>0&&r%30==0?this.logger.warn?.(`[acp-server] agent sync still failing after ${r} attempts — retrying every ${a}s`):r<=i.length?this.logger.info?.(o):this.logger.debug?.(o),this.agentSyncRetryTimer=setTimeout(()=>{this.tryAgentSync(r)},s)},s=Date.now();this.agentSyncInFlight=!0;try{const e=await syncAgentsToPlatform({gateway:this.gateway,agentGatewayUrl:this.config.agentGatewayUrl,capabilities:{canvas:this.config.canvasEnabled,terminal:this.terminalServiceReady,fileSystem:!0,cron:!0,streaming:!0},deliveryMode:this.config.messageBus.enabled?"push":void 0,boundAgentFetch:this.boundAgentFetchFn,logger:this.logger});isAgentSyncWaitForGateway(e)?this.logger.info?.("[acp-server] agent sync waiting for OpenClaw Gateway connection"):e?(this.legacyFencedSyncFailureCount=0,this.core.setAgentMapping(e),this.logger.info?.(`[acp-server] agent mapping loaded: ${[...e.keys()].join(", ")} (sync took ${Date.now()-s}ms)`),this.agentProjection&&this.runtimeServiceFactoryFenced&&this.logger.error?.("[acp-server] agent bootstrap sync recovered Agent Identity while the runtime Service factory was fenced — restart this instance to complete registration")):r()}catch(e){this.logger.warn?.(`[acp-server] agent sync failed: ${e}`),r()}finally{this.agentSyncInFlight=!1}}async connectGateway(e){const t=this.config.gateway,i=new GatewayClient({url:t.url,token:t.token||"",clientId:t.clientId,openclawCanvasCompat:!!this.config.openclawCanvasCompat,retry:{baseMs:this.config.retry.baseMs,maxMs:this.config.retry.maxMs,maxAttempts:this.config.retry.maxAttempts}});if(e&&!this.shouldContinueStart(e))return i.stop(),void i.removeAllListeners();e&&(e.gateway=i),this.gateway=i,i.on("chat",e=>this.events.handleChatEvent(e)),i.on("agent",e=>this.events.handleAgentEvent(e)),i.on("cron",e=>this.events.handleCronEvent(e)),i.on("sessionMessage",e=>this.core.handleSessionMessage(e)),i.on("response",e=>this.core.handleGatewayResponse(e));const n=i;this.canvasOrch.updateDeps({gateway:n?{get isConnected(){return n.isConnected},request:(e,t)=>this.gateway.request(e,t),agentSend:(e,t,i)=>this.gateway.agentSend(e,t,i)}:null}),this.config.openclawCanvasCompat&&(i.on("canvas",e=>this.canvasOrch.handleOpenClawCanvasEvent(e)),this.logger.info?.("[acp-server] OpenClaw native canvas compat enabled — listening for canvas events")),i.on("connected",()=>{this.agentProjection?.onGatewayReconnect(),this.logger.info?.("[acp-server] Connected to OpenClaw Gateway"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"gateway.connected",severity:"info"}),this.core.resubscribeAllSessionMessages(),this.core.refreshCronIsolatedSessionKeys(),this.tryAgentSync()}),i.on("disconnected",()=>{this.logger.warn?.("[acp-server] Gateway disconnected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"gateway.disconnected",severity:"warn"}),this.core.cleanupAllPendingPrompts()}),i.on("reconnecting",e=>{this.logger.info?.(`[acp-server] Reconnecting to Gateway attempt=${e.attempt} delay=${Math.round(e.delayMs)}ms`)}),i.on("reconnect-exhausted",e=>{this.logger.error?.(`[acp-server] Gateway reconnect exhausted after ${e} attempts`)}),i.on("error",e=>{this.logger.error?.("[acp-server] Gateway error:",e.message)}),i.on("transport",e=>{this.logger.info?.(`[acp-server] transport.${e.event} durationMs=${e.durationMs??"?"}`),this.writeObs({component:"gateway-client",domain:"lifecycle",name:`transport.${e.event}`,severity:"connect_fail"===e.event||"error"===e.event?"warn":"debug",payload:e})}),i.on("reconnect-notify-scheduled",e=>{this.logger.info?.(`[acp-server] Server-initiated reconnect scheduled in ${e.delayMs}ms`)}),i.on("reconnect-notify-fired",()=>{this.logger.info?.("[acp-server] Server-initiated reconnect firing")});try{if(e){const t=await this.waitForGeneration(i.start(),e,ACPServer.LIFECYCLE_CONNECT_TIMEOUT_MS);if("cancelled"===t.status)return;if("timed_out"===t.status)throw new Error("Gateway connection timed out")}else await i.start()}catch(e){this.logger.error?.("[acp-server] Initial Gateway connect failed:",e)}}handleConnection(e,t){if(this.client){this.logger.warn?.("[acp-server] Replacing existing ACP client with new connection (takeover)");const e=this.client;this.client=null;const t=this.acpConnectionTerminalHandlers.get(e);t?t.destroy():this.terminalHandler.destroy(),this.acpConnectionTerminalHandlers.delete(e);try{e.close(1008,"replaced by new client")}catch{e.terminate()}}this.client=e;const i=this.createTerminalHandler(new AcpTransport(t=>{if(this.client===e&&e.readyState===WebSocket.OPEN)try{e.send(JSON.stringify(t))}catch(e){this.logger.debug?.("[acp-server] connection terminal send failed",e)}},this.writeObs));this.acpConnectionTerminalHandlers.set(e,i),this.core.initialized=!1,this.logger.info?.("[acp-server] ACP client connected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"client.connected",severity:"info"}),e.on("message",t=>{if(this.client!==e)return;const n="string"==typeof t?t:t.toString("utf-8");try{const e=JSON.parse(n);if("id"in e&&void 0!==e.id){(e.method.startsWith("terminal/")?i.handleRequest(e):this.core.handleRequest(e)).catch(e=>{this.logger.error?.(`[acp-server] request failed: ${e instanceof Error?e.message:String(e)}`)})}else e.method.startsWith("terminal/")?i.handleNotification(e.method,e.params):this.core.handleNotification(e)}catch{this.logger.error?.("[acp-server] Invalid JSON-RPC message")}}),e.on("close",()=>{i.destroy(),this.acpConnectionTerminalHandlers.delete(e),this.client===e&&(this.client=null,this.core.initialized=!1),this.logger.info?.("[acp-server] ACP client disconnected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"client.disconnected",severity:"info"})})}createTerminalHandler(e){const t=new TerminalBridgeHandler({terminalMgr:this.terminalMgr,transport:e,config:this.config,logger:this.logger,writeObs:this.writeObs,localAuthority:this.localTerminalAuthorityRegistry.forPrincipal(this.localTerminalAuthorityRegistry.issuePrincipal()),executeCanonical:(e,t,i,n)=>this.core.dispatchLegacyTerminalCapability(e,t,i,n)});return t.setupListeners(),t}setCanvasState(e,t){this.canvasOrch.setCanvasState(e,t)}wireRelayActionHandler(){this.canvasOrch.wireRelayActionHandler()}isCanvasActive(e){return this.canvasOrch.isCanvasActive(e)}isCanvasActiveForGatewayKey(e){return this.canvasOrch.isCanvasActiveForGatewayKey(e)}broadcastNotification(e,t){this.transport.sendNotification(e,t)}getActiveSessionId(){return this.state.lastActiveSessionId}getSessionIdForToolCall(e){const t=this.pendingToolCallSessionKeys.length>0?this.pendingToolCallSessionKeys[this.pendingToolCallSessionKeys.length-1]:void 0;if(t){const i=this.state.findSessionByKey(t)??this.core.ensureSessionForKey(t);if(i?.sessionId)return e&&this.state.toolCallToSessionMap.set(e,i.sessionId),i.sessionId}if(e){const t=this.state.toolCallToSessionMap.get(e);if(t)return t;const i=this.state.toolCallIdMap.get(e);if(i&&i!==e){const e=this.state.toolCallToSessionMap.get(i);if(e)return e}const n=e.split("_");if(n.length>=3&&"tc"===n[0]){const e=n[1],t=this.state.promptsByRunId.get(e);if(t?.sessionId)return t.sessionId}}this.logger.warn?.(`[acp-server] getSessionIdForToolCall MISS: toolCallId=${e??"(none)"}, sessions=${this.state.sessions.size}, pendingKey=${t??"none"}`)}getSessionIdForActivePrompt(){return this.getSessionIdForToolCall()}setPendingToolCallSessionKey(e){e&&(this.pendingToolCallSessionKeys.push(e),this.pendingToolCallSessionKeys.length>16&&this.pendingToolCallSessionKeys.shift())}preMapToolCallSession(e){const t=this.pendingToolCallSessionKeys.length>0?this.pendingToolCallSessionKeys[this.pendingToolCallSessionKeys.length-1]:void 0;if(!t||!e)return;if(this.state.toolCallToSessionMap.has(e))return;const i=this.state.findSessionByKey(t)??this.core.ensureSessionForKey(t);i?.sessionId&&this.state.toolCallToSessionMap.set(e,i.sessionId)}waitForAgentMapping(){return this.core.waitForAgentMapping()}onceAgentMappingReady(e){this.core.onceAgentMappingReady(e)}onAgentMappingUpdated(e){this.core.onAgentMappingUpdated(e)}flushPendingCanvasLinks(){this.canvasOrch.flushPendingCanvasLinks()}onRelayReconnected(){this.canvasOrch.onRelayReconnected()}getLocalAgentId(){return this.config.gateway.agentId||this.core.getDefaultAgentId()}resolveLocalAgentIdForPlatformAgent(e){return this.core.resolveLocalAgentId(e)}getDefaultPlatformAgentId(){return this.getDefaultPlatformAgent()?.id}getDefaultPlatformAgent(){const e=this.core.agentMapping;if(e&&0!==e.size)return e.values().next().value??void 0}getPlatformAgentMappings(){const e=this.core.agentMapping;return e&&0!==e.size?[...e.entries()].map(([e,t])=>({localAgentId:e,platformAgent:t})):[]}getPlatformAgentIdForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const i=this.state.sessions.get(t);return this.core.resolvePlatformAgentId(i?.agentId??"")??i?.platformAgentId}getGatewaySessionKeyForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const i=this.state.sessions.get(t);return i?.gatewaySessionKey}getMentionsForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const i=this.state.sessions.get(t);return i?.activeMentions}getResourceRenderer(){return this.canvasOrch.resourceRenderer}getDescriptorRegistry(){return this.canvasOrch.descriptorRegistry}getFormBuilder(){return this.canvasOrch.formBuilder}getSchemaRegistry(){return this.canvasOrch.schemaRegistry}registerSkillFormSchemas(e,t){const i=this.canvasOrch.schemaRegistry;i&&import("node:fs/promises").then(async({readFile:n})=>{try{const r=`${t}/skill.json`,s=await n(r,"utf-8"),a=JSON.parse(s);if(a.formSchemas?.length){for(const e of a.formSchemas)i.register({...e,submitLabel:e.submitLabel||"Submit",fields:e.fields});this.logger.info?.(`[acp-server] Registered ${a.formSchemas.length} form schema(s) from skill "${e}"`)}}catch{}}).catch(e=>{this.logger.debug?.(`[acp-server] skill form schema import failed: ${e instanceof Error?e.message:String(e)}`)})}emitCanvasActionAsUserMessage(e,t){this.canvasOrch.emitCanvasActionAsUserMessage(e,t)}stop(){const e=this.teardownContext.getStore();if(e?.server===this){const t=e.generation;return this.activeGeneration!==t?t.stopPromise??t.teardownPromise??Promise.resolve():(e.detachOwner(),t.stopPromise??Promise.resolve())}const t=this.activeGeneration;if(!t||"stopped"===this.lifecyclePhase)return Promise.resolve();if("stopping"===this.lifecyclePhase)return t.stopPromise??Promise.resolve();let i,n;const r=new Promise((e,t)=>{i=e,n=t});t.stopPromise=r,this.lifecyclePhase="stopping",t.abortController.abort();const s=this.teardownGeneration(t);return(async()=>{try{t.startPromise.catch(()=>{}),await s,i()}catch(e){n(e)}finally{this.activeGeneration===t&&(this.activeGeneration=void 0,this.lifecyclePhase="stopped")}})(),r}teardownGeneration(e){return e.teardownPromise||(e.teardownPromise=this.teardownGenerationOnce(e)),e.teardownPromise}async teardownGenerationOnce(e){const t=[],i=new AbortController;let n,r=!1;const s=new Promise(e=>{n=setTimeout(()=>{r=!0,i.abort(),e()},ACPServer.LIFECYCLE_TEARDOWN_TIMEOUT_MS),n.unref?.()}),a=async(n,r)=>{let a;const o=new Promise(e=>{a=e}),c=Promise.resolve().then(()=>this.teardownContext.run({server:this,generation:e,detachOwner:a},()=>r(i.signal))),l=await Promise.race([c.then(()=>({status:"completed"}),e=>({status:"failed",error:e})),s.then(()=>({status:"timed_out"})),o.then(()=>({status:"detached"}))]);if("failed"===l.status)t.push(l.error);else if("timed_out"===l.status){const i=new Error(`lifecycle teardown timed out: ${n}`);t.push(i),this.logger.error?.(`[acp-server] ${i.message} generation=${e.id}`),this.writeObs({component:"acp-server",domain:"lifecycle",name:"lifecycle.teardown_timeout",severity:"error",payload:{owner:n,generation:e.id,timeoutMs:ACPServer.LIFECYCLE_TEARDOWN_TIMEOUT_MS}})}},o=[];this.setTerminalServiceReady(e,!1),o.push(a("session-mutation-coordinator",()=>this.sessionMutationCoordinator.fenceActive("gateway_disconnected"))),this.agentSyncRetryTimer&&(clearTimeout(this.agentSyncRetryTimer),this.agentSyncRetryTimer=null),e.projectionStarted&&(e.projectionStarted=!1,o.push(a("agent-projection",()=>this.agentProjection?.stop()))),o.push(a("session-state",()=>this.state.destroy())),o.push(a("canvas-orchestrator",()=>this.canvasOrch.destroy())),o.push(a("mcp-receipt-history",()=>this.core.setRuntimeReceiptHistoryPort(void 0)));const c=e.gateway,l=e.bridge,h=e.bridgeSocket,g=e.terminalOutbound,d=e.runtimeConsumer,u=this.client&&this.client!==h?this.client:null,m=this.wss;if(e.gateway=void 0,e.bridge=void 0,e.bridgeSocket=void 0,e.terminalOutbound=void 0,e.runtimeConsumer=void 0,this.gateway===c&&(this.gateway=null),this.bridgeInstance===l&&(this.bridgeInstance=null),this.client!==u&&this.client!==h||(this.client=null),this.terminalOutbound===g&&(this.terminalOutbound=null),this.runtimeRpcConsumer===d&&(this.runtimeRpcConsumer=void 0),this.wss=null,this.bridgeConsecutiveFailures=0,c&&o.push(a("gateway",async()=>{try{await c.stop()}finally{c.removeAllListeners()}})),u&&o.push(a("incoming-client",()=>{u.removeAllListeners(),u.close()})),l&&o.push(a("bridge",async()=>{try{await this.closeBridgeOnce(l)}catch(e){this.logger.debug?.(`[acp-server] bridge.close failed during stop: ${e instanceof Error?e.message:String(e)}`)}})),g&&o.push(a("terminal-outbound",e=>g.drain({timeoutMs:ACPServer.LIFECYCLE_TEARDOWN_TIMEOUT_MS,terminateAfterTimeout:!0,signal:e}))),e.authorityOwned&&(e.authorityOwned=!1,o.push(a("runtime-authority",()=>e.authority?.stop()))),d&&o.push(a("runtime-consumer",e=>this.closeConsumerOnce(d,e))),m&&o.push(a("websocket-server",()=>{m.removeAllListeners(),m.close()})),await Promise.all(o),await a("runtime-authority-reset",()=>e.authority?.setRuntimeConsumerLive(!1)),await a("terminal-handler",()=>this.terminalHandler.destroy()),await a("terminal-manager",()=>this.terminalMgr.destroy()),n&&clearTimeout(n),r||i.abort(),this.logger.info?.("[acp-server] Stopped"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"lifecycle.shutdown",severity:"info"}),t.length>0)throw t[0]}}
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mkdirSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{dirname,join}from"node:path";export const CRON_DURATION_FLOOR_MS=3e4;export const CRON_DURATION_CAP_DEFAULT_MS=72e5;function trimStr(e){if("string"==typeof e){const r=e.trim();return r.length>0?r:void 0}}function readInt(e){if("number"==typeof e&&Number.isFinite(e))return Math.trunc(e);if("string"==typeof e){const r=parseInt(e,10);return Number.isFinite(r)?r:void 0}}function readBool(e){return"boolean"==typeof e?e:"true"===e||"false"!==e&&void 0}function pick(e,r){for(const r of e)if(void 0!==r.value)return{value:r.value,source:r.source};return r}const CURRENT_CONFIG_PROTOCOL=1;function shouldTrustOperationalConfig(e){const r=e._generator;if(!r)return!1;return("number"==typeof r.configProtocol?r.configProtocol:0)>=1}const DEFAULT_GATEWAY_URL="ws://127.0.0.1:18789",DEFAULT_LOG_DIR=join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos-logs"),DEFAULT_OPENCLAW_CONFIG_PATH=join(homedir(),".openclaw","openclaw.json");export function resolveConfigWithMeta(e){const r=e.pluginConfig??{},a=e.openclawConfig??{},o=e.openclawConfigPath??DEFAULT_OPENCLAW_CONFIG_PATH,u=shouldTrustOperationalConfig(r),l=pick([{source:"pluginConfig",value:trimStr(r.agentGatewayUrl)},{source:"pluginConfig",value:trimStr(r.platformUrl)},{source:"env",value:trimStr(process.env.AGENT_GATEWAY_URL)}],{source:"default",value:""}),n=r.gateway??{},s=(a.gateway??{}).auth??{},t=pick([...u?[{source:"pluginConfig",value:trimStr(n.url)}]:[],{source:"env",value:trimStr(process.env.BEEOS_GATEWAY_URL)}],{source:"default",value:DEFAULT_GATEWAY_URL}),i=pick([{source:"openclawConfig",value:trimStr(s.token)},{source:"pluginConfig",value:trimStr(n.token)},{source:"env",value:trimStr(process.env.BEEOS_GATEWAY_TOKEN)}],{source:"default",value:""}),c=pick([...u?[{source:"pluginConfig",value:readInt(n.protocol)}]:[]],{source:"default",value:3}),v=pick([...u?[{source:"pluginConfig",value:trimStr(n.clientId)}]:[]],{source:"default",value:`beeos-claw-${process.pid}`}),p=pick([...u?[{source:"pluginConfig",value:trimStr(n.agentId)}]:[]],{source:"default",value:""}),d=r.bridge??{},g=pick([{source:"pluginConfig",value:trimStr(r.keyFile)},{source:"pluginConfig",value:trimStr(d.keyFile)},{source:"env",value:trimStr(process.env.BRIDGE_KEY_FILE)}],{source:"default",value:""}),f=pick([{source:"env",value:trimStr(process.env.BRIDGE_URL)}],{source:"default",value:""}),E=pick([...u?[{source:"pluginConfig",value:trimStr(d.service)}]:[],{source:"env",value:trimStr(process.env.BRIDGE_SERVICE)}],{source:"default",value:"acp"}),m=d.shell??{},_=pick([...u?[{source:"pluginConfig",value:readBool(m.enabled)}]:[],{source:"env",value:readBool(process.env.BEEOS_SHELL_ENABLED)}],{source:"default",value:!0}),S=r.retry??{},A=pick([...u?[{source:"pluginConfig",value:readInt(S.baseMs)}]:[]],{source:"default",value:1e3}),C=pick([...u?[{source:"pluginConfig",value:readInt(S.maxMs)}]:[]],{source:"default",value:6e5}),I=pick([...u?[{source:"pluginConfig",value:readInt(S.maxAttempts)}]:[]],{source:"default",value:0}),M=r.log??{},O=pick([...u?[{source:"pluginConfig",value:readBool(M.enabled)}]:[],{source:"env",value:readBool(process.env.BEEOS_LOG_ENABLED)}],{source:"default",value:!0}),T=pick([...u?[{source:"pluginConfig",value:readBool(M.verbose)}]:[]],{source:"default",value:!1}),R=pick([{source:"pluginConfig",value:trimStr(M.dir)}],{source:"default",value:DEFAULT_LOG_DIR}),B=pick([...u?[{source:"pluginConfig",value:readInt(d.promptTimeoutMs)}]:[],{source:"env",value:readInt(process.env.BEEOS_PROMPT_TIMEOUT_MS)}],{source:"default",value:6e5}),k=pick([...u?[{source:"pluginConfig",value:readInt(r.terminalWsPort)}]:[],{source:"env",value:readInt(process.env.BEEOS_TERMINAL_WS_PORT)}],{source:"default",value:18801}),
|
|
1
|
+
import{mkdirSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{dirname,join}from"node:path";export const CRON_DURATION_FLOOR_MS=3e4;export const CRON_DURATION_CAP_DEFAULT_MS=72e5;function trimStr(e){if("string"==typeof e){const r=e.trim();return r.length>0?r:void 0}}function readInt(e){if("number"==typeof e&&Number.isFinite(e))return Math.trunc(e);if("string"==typeof e){const r=parseInt(e,10);return Number.isFinite(r)?r:void 0}}function readBool(e){return"boolean"==typeof e?e:"true"===e||"false"!==e&&void 0}function pick(e,r){for(const r of e)if(void 0!==r.value)return{value:r.value,source:r.source};return r}const CURRENT_CONFIG_PROTOCOL=1;function shouldTrustOperationalConfig(e){const r=e._generator;if(!r)return!1;return("number"==typeof r.configProtocol?r.configProtocol:0)>=1}const DEFAULT_GATEWAY_URL="ws://127.0.0.1:18789",DEFAULT_LOG_DIR=join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos-logs"),DEFAULT_OPENCLAW_CONFIG_PATH=join(homedir(),".openclaw","openclaw.json");export function resolveConfigWithMeta(e){const r=e.pluginConfig??{},a=e.openclawConfig??{},o=e.openclawConfigPath??DEFAULT_OPENCLAW_CONFIG_PATH,u=shouldTrustOperationalConfig(r),l=pick([{source:"pluginConfig",value:trimStr(r.agentGatewayUrl)},{source:"pluginConfig",value:trimStr(r.platformUrl)},{source:"env",value:trimStr(process.env.AGENT_GATEWAY_URL)}],{source:"default",value:""}),n=r.gateway??{},s=(a.gateway??{}).auth??{},t=pick([...u?[{source:"pluginConfig",value:trimStr(n.url)}]:[],{source:"env",value:trimStr(process.env.BEEOS_GATEWAY_URL)}],{source:"default",value:DEFAULT_GATEWAY_URL}),i=pick([{source:"openclawConfig",value:trimStr(s.token)},{source:"pluginConfig",value:trimStr(n.token)},{source:"env",value:trimStr(process.env.BEEOS_GATEWAY_TOKEN)}],{source:"default",value:""}),c=pick([...u?[{source:"pluginConfig",value:readInt(n.protocol)}]:[]],{source:"default",value:3}),v=pick([...u?[{source:"pluginConfig",value:trimStr(n.clientId)}]:[]],{source:"default",value:`beeos-claw-${process.pid}`}),p=pick([...u?[{source:"pluginConfig",value:trimStr(n.agentId)}]:[]],{source:"default",value:""}),d=r.bridge??{},g=pick([{source:"pluginConfig",value:trimStr(r.keyFile)},{source:"pluginConfig",value:trimStr(d.keyFile)},{source:"env",value:trimStr(process.env.BRIDGE_KEY_FILE)}],{source:"default",value:""}),f=pick([{source:"env",value:trimStr(process.env.BRIDGE_URL)}],{source:"default",value:""}),E=pick([...u?[{source:"pluginConfig",value:trimStr(d.service)}]:[],{source:"env",value:trimStr(process.env.BRIDGE_SERVICE)}],{source:"default",value:"acp"}),m=d.shell??{},_=pick([...u?[{source:"pluginConfig",value:readBool(m.enabled)}]:[],{source:"env",value:readBool(process.env.BEEOS_SHELL_ENABLED)}],{source:"default",value:!0}),S=r.retry??{},A=pick([...u?[{source:"pluginConfig",value:readInt(S.baseMs)}]:[]],{source:"default",value:1e3}),C=pick([...u?[{source:"pluginConfig",value:readInt(S.maxMs)}]:[]],{source:"default",value:6e5}),I=pick([...u?[{source:"pluginConfig",value:readInt(S.maxAttempts)}]:[]],{source:"default",value:0}),M=r.log??{},O=pick([...u?[{source:"pluginConfig",value:readBool(M.enabled)}]:[],{source:"env",value:readBool(process.env.BEEOS_LOG_ENABLED)}],{source:"default",value:!0}),T=pick([...u?[{source:"pluginConfig",value:readBool(M.verbose)}]:[]],{source:"default",value:!1}),R=pick([{source:"pluginConfig",value:trimStr(M.dir)}],{source:"default",value:DEFAULT_LOG_DIR}),B=pick([...u?[{source:"pluginConfig",value:readInt(d.promptTimeoutMs)}]:[],{source:"env",value:readInt(process.env.BEEOS_PROMPT_TIMEOUT_MS)}],{source:"default",value:6e5}),k=pick([...u?[{source:"pluginConfig",value:readInt(r.terminalWsPort)}]:[],{source:"env",value:readInt(process.env.BEEOS_TERMINAL_WS_PORT)}],{source:"default",value:18801}),y=(u?trimStr(r.terminalWsAllowedOrigins):void 0)??trimStr(process.env.BEEOS_TERMINAL_WS_ALLOWED_ORIGINS),L=y?y.split(",").map(e=>e.trim()).filter(Boolean):void 0,N=pick([{source:y?"pluginConfig":"default",value:L?.length?L:void 0}],{source:"default",value:["localhost","127.0.0.1","::1"]}),w=pick([{source:"env",value:readBool(process.env.BEEOS_TERMINAL_SERVICE_ENABLED)},...u?[{source:"pluginConfig",value:readBool(r.terminalServiceEnabled)}]:[]],{source:"default",value:!1}),b=pick([...u?[{source:"pluginConfig",value:readBool(r.legacyTerminalWsEnabled)}]:[],{source:"env",value:readBool(process.env.BEEOS_LEGACY_TERMINAL_WS_ENABLED)}],{source:"default",value:!1}),h=pick([...u?[{source:"pluginConfig",value:readInt(r.streamIdleTimeoutMs)}]:[],{source:"env",value:readInt(process.env.BEEOS_STREAM_IDLE_TIMEOUT_MS)}],{source:"default",value:15e3}),U=pick([...u?[{source:"pluginConfig",value:readInt(r.streamStaleTimeoutMs)}]:[],{source:"env",value:readInt(process.env.BEEOS_STREAM_STALE_TIMEOUT_MS)}],{source:"default",value:6e5}),D=pick([...u?[{source:"pluginConfig",value:readInt(r.cronSignalWindowMs)}]:[],{source:"env",value:readInt(process.env.BEEOS_CRON_SIGNAL_WINDOW_MS)}],{source:"default",value:6e4}),G=pick([{source:"pluginConfig",value:readInt(r.cronMaxDurationDefaultMs)},{source:"env",value:readInt(process.env.BEEOS_CRON_MAX_DURATION_DEFAULT_MS)}],{source:"default",value:6e5}),W=pick([{source:"pluginConfig",value:readInt(r.cronMaxDurationCapMs)},{source:"env",value:readInt(process.env.BEEOS_CRON_MAX_DURATION_CAP_MS)}],{source:"default",value:72e5}),P=pick([{source:"env",value:trimStr(process.env.CANVAS_RELAY_URL)},{source:"pluginConfig",value:trimStr(r.canvasRelayUrl)}],{source:"default",value:"https://canvas-relay.beeos.ai"}),x=pick([...u?[{source:"pluginConfig",value:readBool(r.canvasEnabled)}]:[],{source:"env",value:readBool(process.env.BEEOS_CANVAS_ENABLED)}],{source:"default",value:!0}),F=pick([...u?[{source:"pluginConfig",value:readBool(r.openclawCanvasCompat)}]:[],{source:"env",value:readBool(process.env.BEEOS_OPENCLAW_CANVAS_COMPAT)}],{source:"default",value:!1}),H=r.messageBus??{},Y=pick([{source:"pluginConfig",value:trimStr(H.serviceUrl)},{source:"env",value:trimStr(process.env.MESSAGE_SERVICE_URL)}],{source:"default",value:""}),j=pick([{source:"pluginConfig",value:trimStr(H.principalId)},{source:"env",value:trimStr(process.env.BEEOS_PRINCIPAL_ID)}],{source:"default",value:""}),$=pick([{source:"pluginConfig",value:readBool(H.a2aEnabled)},{source:"env",value:readBool(process.env.BEEOS_A2A_ENABLED)}],{source:"default",value:!0}),V=pick([{source:"pluginConfig",value:readBool(H.enabled)},{source:"env",value:readBool(process.env.BEEOS_MESSAGE_BUS_ENABLED)}],{source:"default",value:!(!Y.value&&!l.value)}),K={agentGatewayUrl:l.value.replace(/\/+$/,""),keyFile:g.value,bridge:{url:f.value,service:E.value,mode:trimStr(d.mode),shell:{enabled:_.value},promptTimeoutMs:B.value},gateway:{url:t.value,token:i.value,protocol:c.value,clientId:v.value,agentId:p.value},retry:{baseMs:Math.max(0,A.value),maxMs:Math.max(0,C.value),maxAttempts:Math.max(0,I.value)},log:{enabled:O.value,verbose:T.value,dir:R.value},promptTimeoutMs:B.value,terminalWsPort:k.value,terminalWsAllowedOrigins:N.value,terminalServiceEnabled:w.value,legacyTerminalWsEnabled:b.value,streamIdleTimeoutMs:Math.max(0,h.value),streamStaleTimeoutMs:Math.max(0,U.value),cronSignalWindowMs:Math.max(0,D.value),cronMaxDurationDefaultMs:Math.max(3e4,G.value),cronMaxDurationCapMs:Math.max(6e4,W.value),canvasRelayUrl:P.value.replace(/\/+$/,""),canvasEnabled:x.value,openclawCanvasCompat:F.value,messageBus:{enabled:V.value,serviceUrl:Y.value,principalId:j.value,a2aEnabled:$.value}},X={agentGatewayUrl:l.source,keyFile:g.source,bridge:{url:f.source,service:E.source,shell:{enabled:_.source}},gateway:{url:t.source,token:i.source,protocol:c.source,clientId:v.source,agentId:p.source},retry:{baseMs:A.source,maxMs:C.source,maxAttempts:I.source},log:{enabled:O.source,verbose:T.source,dir:R.source},promptTimeoutMs:B.source,terminalWsPort:k.source,terminalWsAllowedOrigins:N.source,terminalServiceEnabled:w.source,legacyTerminalWsEnabled:b.source,streamIdleTimeoutMs:h.source,streamStaleTimeoutMs:U.source,cronSignalWindowMs:D.source,cronMaxDurationDefaultMs:G.source,cronMaxDurationCapMs:W.source,canvasRelayUrl:P.source,canvasEnabled:x.source,openclawCanvasCompat:F.source,messageBus:{enabled:V.source,serviceUrl:Y.source,principalId:j.source,a2aEnabled:$.source}},q=[];return K.agentGatewayUrl||q.push({code:"CONFIG_AGENT_GATEWAY_URL_MISSING",severity:"error",message:"agentGatewayUrl is required for file uploads and Agent Gateway callbacks",nextSteps:[`Set plugins.entries.beeos-claw.config.agentGatewayUrl in ${o}`,"Or set the AGENT_GATEWAY_URL environment variable"]}),K.gateway.token||q.push({code:"CONFIG_GATEWAY_TOKEN_MISSING",severity:"warn",message:"gateway.token is empty; Gateway auth may fail if authentication is enabled",nextSteps:[`Set gateway.auth.token in ${o}`,`Or set plugins.entries.beeos-claw.config.gateway.token in ${o}`]}),{config:K,sources:X,validation:q,openclawConfigPath:o}}export function resolveConfig(e){const{config:r,validation:a}=resolveConfigWithMeta({pluginConfig:e}),o=a.find(e=>"error"===e.severity);if(o)throw new Error(`[beeos-claw] ${o.message}`);return r}const MIRROR_PATH=join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos-claw","openclaw.json");export function mirrorOpenClawConfig(e){if(!e||"object"!=typeof e||Array.isArray(e))return{copied:!1,destinationPath:MIRROR_PATH,reason:"config_invalid"};try{return mkdirSync(dirname(MIRROR_PATH),{recursive:!0}),writeFileSync(MIRROR_PATH,JSON.stringify(e,null,2)+"\n",{encoding:"utf8",mode:384}),{copied:!0,destinationPath:MIRROR_PATH}}catch(e){return{copied:!1,destinationPath:MIRROR_PATH,reason:e instanceof Error?e.message:"write_failed"}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash,randomUUID}from"node:crypto";import{dispatchRuntimeReplyWithRoute,hasRuntimeReplyApi}from"./runtime/reply-router.js";import{createImReplySink}from"./runtime/im-reply-sink.js";import{SerializedCallbackQueue}from"./runtime/callback-queue.js";import{promptRunKey}from"./runtime/prompt-run-store.js";import{classifyAgentRuntimeFailure}from"./runtime/agent-runtime-failure-classifier.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"./runtime/session-mutation-coordinator.js";import{interceptAgentRuntimeFailures,messageStopReasonForRuntimeFailure,SESSION_BUSY_PUBLIC_MESSAGE}from"./runtime/im-runtime-failure-policy.js";import{maybeRenameImConversationTitle}from"./runtime/conversation-title.js";const normalizePromptTimeoutMs=e=>{if("number"!=typeof e||!Number.isFinite(e))return;const n=Math.max(0,Math.trunc(e));return n>0?n:void 0},ABORT_TERMINAL_GRACE_MS=1e4,formatErrMessage=e=>{if(e instanceof Error)return e.message;if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}},deterministicMessageId=(e,n,a,r)=>`${e}_${createHash("sha256").update(`${n}:${a}:${r}`).digest("hex").slice(0,32)}`;export const dispatchImInboundPrompt=async e=>{const{runtime:n,prompt:a,client:r,logger:t,promptRunStore:i,sessionMutationCoordinator:s,abortTransport:o,activeRunAuthority:c}=e;if(!hasRuntimeReplyApi(n))return t.warn?.(`[im] runtime dispatch skipped reason=reply_api_unavailable channel=${a.channelId} in_reply_to=${a.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_api_unavailable",channelId:a.channelId,inboundMessageId:a.inboundMessageId}),!1;const d=`im_req_${randomUUID().replace(/-/g,"")}`,l=randomUUID(),u=Date.now(),m=e.origin??"im",p={protocol:"im",requestId:d,sessionId:a.sessionKey??"agent:main",sourceMessageId:a.inboundMessageId,chatId:a.channelId,origin:m},I=normalizePromptTimeoutMs(e.promptTimeoutMs),g=promptRunKey(a.channelId,a.inboundMessageId),h=a.sessionKey??"agent:main",y=new AbortController;let f,_,E;const R=new Promise((e,n)=>{E=n});let b;const w=()=>{void 0!==b&&(clearTimeout(b),b=void 0)},$=async e=>{if(_??=e,y.abort(e),!f)return{ok:!0};const n=await s.requestCancel(f,o);return n.ok?n.terminalObservation.then(e=>{e.ok||E(new Error(SESSION_RECOVERY_REQUIRED_CODE))}):E(new Error(SESSION_RECOVERY_REQUIRED_CODE)),{ok:n.ok}};if(!i.register(g,{requestId:d,channelId:a.channelId,requestCancel:$,startedAt:u,sessionKey:h}))return t.warn?.(`[im] runtime dispatch rejected duplicate inboundMessageId=${a.inboundMessageId} requestId=${d} channel=${a.channelId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:"duplicate_inbound_message_id",cause:"abort"}),!1;const M=r.messages.startStream({conversationId:a.channelId,id:deterministicMessageId("asst",a.channelId,a.inboundMessageId,a.selfPrincipalId),replyTo:e.inReplyTo??a.inboundMessageId,type:"agent_reply"},{onError:(e,n)=>{const r="terminal"===n?t.warn:t.debug;r?.(`[im] runtime stream ${n} PATCH error requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${e.message}`)}},{sender:a.selfPrincipalId});let S,v=!1;try{v=!0===(await M.opened()).idempotent}catch(e){t.debug?.(`[im] runtime reply stream open probe failed requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}; continuing so terminal fallback can land an error row`)}if(v)return t.info?.(`[im] runtime dispatch skipped existing deterministic reply requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"idempotent_reply_exists",channelId:a.channelId,inboundMessageId:a.inboundMessageId}),i.unregister(g),!1;const q=new SerializedCallbackQueue,C=createImReplySink({stream:M,openReasoningStream:()=>(S=r.messages.startStream({conversationId:a.channelId,id:deterministicMessageId("rsn",a.channelId,a.inboundMessageId,a.selfPrincipalId),replyTo:e.inReplyTo??a.inboundMessageId,type:"agent_reasoning"},{onError:(e,n)=>{const r="terminal"===n?t.warn:t.debug;r?.(`[im] runtime reasoning stream ${n} PATCH error requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${e.message}`)}},{sender:a.selfPrincipalId}),S),abortSignal:y.signal,callbackQueue:q,logger:t,requestId:d,runId:l,channelId:a.channelId,inboundMessageId:a.inboundMessageId,origin:m,onAgentRunStart:e=>{s.bindRun(f,e)}}),k=interceptAgentRuntimeFailures(C,"im_runtime",e=>t.warn?.(`[im] runtime failure reply suppressed requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} error_code=${e.errorCode}`)),O=k.sink,T=async(e,n)=>{if(!S)return!1;try{return"cancel"===e?await S.cancel({stopReason:"user_stop"}):"fail"===e?await S.fail(n?{stopReason:n}:void 0):await S.finalize({stopReason:"end_turn"}),!1}catch(e){return t.warn?.(`[im] runtime reasoning stream terminal write failed requestId=${d} channel=${a.channelId}: ${formatErrMessage(e)}`),!0}};let A,D,U=!1;const K=async e=>{O.dispatcher.markComplete(e);try{await O.dispatcher.waitForIdle()}catch{}},N=()=>{w();const n=normalizePromptTimeoutMs(e.abortTerminalGraceMs)??1e4;b=setTimeout(()=>{b=void 0,(async()=>{if(M.isTerminated)return;const e=y.signal.reason??_,n=void 0!==D,r=!n&&"string"==typeof e&&e.startsWith("chat_cancel");if(await K("failure"),!M.isTerminated)try{r?(await T("cancel"),await M.cancel({stopReason:"user_stop"})):n?(await T("fail","timeout"),await M.fail({body:D?.message??"IM runtime dispatch timed out",stopReason:"timeout"})):(await T("fail"),await M.fail({body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"})),t.warn?.(`[im] abort settlement watchdog terminalized reply requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} user_cancel=${r} timeout=${n}`)}catch(e){t.warn?.(`[im] abort settlement watchdog terminal write failed requestId=${d} channel=${a.channelId}: ${formatErrMessage(e)}`)}})()},n)};y.signal.aborted?N():y.signal.addEventListener("abort",N,{once:!0});const P=()=>{if(!y.signal.aborted)return;const e=y.signal.reason;throw e instanceof Error?e:new Error(String(e??"runtime dispatch aborted"))};let F,L,Q;const z=async e=>{if(!f)throw new Error("session_mutation_not_acquired");const n=await s.converge(f,e);return n.ok?(F=n.permit,!0):(t.error?.(`[im] session mutation recovery required requestId=${d} sessionKey=${h} reason=${n.recovery.reason}`),L={code:n.recovery.code,reason:n.recovery.reason,sessionKey:n.recovery.sessionKey,operationId:n.recovery.operationId,surface:"message_service",...n.recovery.terminalKind?{terminalKind:n.recovery.terminalKind}:{}},!1)};try{P();const o=s.tryStartInteractive({sessionKey:h,label:"im_chat_message"},"message_service");if(!o.ok){try{await M.fail({body:SESSION_BUSY_PUBLIC_MESSAGE,stopReason:"error"})}catch(e){t.warn?.(`[im] session busy terminal write failed channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}`)}return t.warn?.(`[im] session write gate busy sessionKey=${h} channel=${a.channelId} in_reply_to=${a.inboundMessageId} active_operation=${o.activeOperationId??"unknown"}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:"session_busy",cause:"runtime_error"}),!1}if(f=o.token,s.bindRun(f,l),c){const e=s.snapshot(h);Q={runtimeConversationUid:a.channelId,runtimeSessionKey:a.channelId,historyGeneration:c.historyGeneration(a.channelId),targetMessageId:a.inboundMessageId,runtimeRunId:l,activeRunRegistrationId:`im:${e?.operationId??l}`},c.activeRuns.register(Q,async()=>{const e=`chat_cancel:activeRun:${Q.activeRunRegistrationId}`,n=await i.abort(g,e);return n.ok||"already_aborted"===n.reason?"accepted":"terminal_committing"===n.reason||"not_found"===n.reason?"already_terminal":"recovery_required"})}P(),t.info?.(`[im] runtime dispatching requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} origin=${m}`),e.onLifecycleEvent?.({type:"dispatch_start",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId});const y=dispatchRuntimeReplyWithRoute({runtime:n,pluginConfig:e.pluginConfig,preparedPrompt:a.dispatchInput,replyRoute:p,replySinks:{im:O},onDispatchInvoked:()=>{U=!0}});void 0!==I&&(A=setTimeout(()=>{D=new Error(`IM runtime dispatch timed out after ${I}ms`),t.warn?.(`[im] runtime dispatch timeout requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} timeout_ms=${I}`),$(D)},I));const _=Promise.race([y,R]),E=await _.finally(()=>{A&&(clearTimeout(A),A=void 0)}),b=k.detectedFailure();if(b)throw new Error(b.diagnosticMessage);if(P(),await K("success"),P(),!i.claimTerminal(g,"completion")){if(P(),"cancelling"===i.phase(g))throw new Error("cancellation in progress");throw new Error("terminal outcome already claimed")}if(!await z("completed"))return await T("fail"),await M.fail({body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:"runtime_error",...L?{recovery:L}:{}}),!1;const w=await T("complete");let S=!1;try{await M.finalize({stopReason:"end_turn"})}catch(e){t.warn?.(`[im] runtime reply stream finalize failed requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}; retrying`);try{await M.finalize({stopReason:"end_turn"})}catch(e){S=!0,t.warn?.(`[im] runtime reply stream finalize retry failed requestId=${d} channel=${a.channelId}: ${formatErrMessage(e)}; falling back to fail()`);try{await M.fail({stopReason:"error"}),S=!1}catch(e){t.warn?.(`[im] runtime reply stream fail fallback failed requestId=${d} channel=${a.channelId}: ${formatErrMessage(e)}`)}}}c&&Q&&c.activeRuns.markTerminal(Q,"completed"),t.info?.(`[im] runtime dispatch completed requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} counts=${JSON.stringify(E.counts)} elapsed_ms=${Date.now()-u}`),e.onLifecycleEvent?.({type:"dispatch_complete",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,counts:E.counts,...S?{terminalWriteFailed:!0}:{},...w?{reasoningTerminalWriteFailed:!0}:{}});const v="string"==typeof a.dispatchInput.message?a.dispatchInput.message:"",q=O.dispatcher.getAccumulatedBody?.()??"";return maybeRenameImConversationTitle({client:r,conversationId:a.channelId,question:v,answer:q,logger:t}),!0}catch(n){await K("failure");const r=y.signal.aborted,s=r?y.signal.reason:void 0,o="cancelling"===i.phase(g),l=void 0!==D,u=s??_,m=!l&&"string"==typeof u&&u.startsWith("chat_cancel");let p="runtime_error";l?p="timeout":(r||o)&&(p="abort");const I=o||m||l||r?"cancellation":"completion";if(!i.claimTerminal(g,I)){if(t.warn?.(`[im] terminal claim raced requestId=${d} channel=${a.channelId}; best-effort envelope terminal if still streaming`),!M.isTerminated)try{m?(await T("cancel"),await M.cancel({stopReason:"user_stop"})):l?(await T("fail","timeout"),await M.fail({body:D?.message??formatErrMessage(n),stopReason:"timeout"})):(await T("fail"),await M.fail({body:formatErrMessage(n),stopReason:"error"}))}catch(e){t.warn?.(`[im] raced terminal write failed requestId=${d}: ${formatErrMessage(e)}`)}throw n}const h=m?"cancelled":l?"timeout":"error";if(c&&Q&&c.activeRuns.markTerminal(Q,"cancelled"===h?"cancelled":"failed"),f&&!await z(h))throw m?(await T("cancel"),await M.cancel({stopReason:"user_stop"}).catch(()=>{})):(await T("fail"),await M.fail({body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{})),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:p,...L?{recovery:L}:{}}),n;const E=classifyAgentRuntimeFailure({raw:n,source:"im_runtime",runtimeKind:"openclaw",requestAborted:m}),R=m?E:k.detectedFailure()??E;m?await T("cancel"):l?await T("fail","timeout"):await T("fail");try{m?await M.cancel({stopReason:"user_stop"}):await M.fail({body:R.publicMessage,stopReason:messageStopReasonForRuntimeFailure(R)})}catch(e){t.warn?.(`[im] runtime stream terminal write failed requestId=${d} channel=${a.channelId}: ${formatErrMessage(e)}`)}throw t.warn?.(`[im] runtime dispatch failed requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} cause=${p} dispatchInvoked=${U} error_code=${R.errorCode}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:R.errorCode,cause:p}),n}finally{w(),i.unregister(g),U?F&&s.release(F):s.releaseBeforeRuntime(f)}};
|
|
1
|
+
import{createHash,randomUUID}from"node:crypto";import{dispatchRuntimeReplyWithRoute,hasRuntimeReplyApi}from"./runtime/reply-router.js";import{createImReplySink}from"./runtime/im-reply-sink.js";import{SerializedCallbackQueue}from"./runtime/callback-queue.js";import{promptRunKey}from"./runtime/prompt-run-store.js";import{classifyAgentRuntimeFailure}from"./runtime/agent-runtime-failure-classifier.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"./runtime/session-mutation-coordinator.js";import{interceptAgentRuntimeFailures,messageStopReasonForRuntimeFailure,SESSION_BUSY_PUBLIC_MESSAGE}from"./runtime/im-runtime-failure-policy.js";import{maybeRenameImConversationTitle}from"./runtime/conversation-title.js";const normalizePromptTimeoutMs=e=>{if("number"!=typeof e||!Number.isFinite(e))return;const n=Math.max(0,Math.trunc(e));return n>0?n:void 0},ABORT_TERMINAL_GRACE_MS=1e4,formatErrMessage=e=>{if(e instanceof Error)return e.message;if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}},failMessageStream=async(e,n)=>{void 0!==n.body&&e.setBody(n.body),await e.fail(void 0===n.stopReason?void 0:{stopReason:n.stopReason})};export const deterministicReplyMessageId=(e,n,t,a)=>`${e}_${createHash("sha256").update(`${n}:${t}:${a}`).digest("hex").slice(0,32)}`;export const dispatchImInboundPrompt=async e=>{const{runtime:n,prompt:t,client:a,logger:r,promptRunStore:i,sessionMutationCoordinator:s,abortTransport:o,activeRunAuthority:c}=e;if(!hasRuntimeReplyApi(n))return r.warn?.(`[im] runtime dispatch skipped reason=reply_api_unavailable channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_api_unavailable",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),!1;const d=`im_req_${randomUUID().replace(/-/g,"")}`,l=randomUUID(),u=Date.now(),m=e.origin??"im",p={protocol:"im",requestId:d,sessionId:t.sessionKey??"agent:main",sourceMessageId:t.inboundMessageId,chatId:t.channelId,origin:m},I=normalizePromptTimeoutMs(e.promptTimeoutMs),g=promptRunKey(t.channelId,t.inboundMessageId),h=t.sessionKey??"agent:main",y=new AbortController;let _,f,R;const E=new Promise((e,n)=>{R=n});let w;const b=()=>{void 0!==w&&(clearTimeout(w),w=void 0)},M=async e=>{if(f??=e,y.abort(e),!_)return{ok:!0};const n=await s.requestCancel(_,o);return n.ok?n.terminalObservation.then(e=>{e.ok||R(new Error(SESSION_RECOVERY_REQUIRED_CODE))}):R(new Error(SESSION_RECOVERY_REQUIRED_CODE)),{ok:n.ok}};if(!i.register(g,{requestId:d,channelId:t.channelId,requestCancel:M,startedAt:u,sessionKey:h}))return r.warn?.(`[im] runtime dispatch rejected duplicate inboundMessageId=${t.inboundMessageId} requestId=${d} channel=${t.channelId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"duplicate_inbound_message_id",cause:"abort"}),!1;const $=deterministicReplyMessageId("asst",t.channelId,t.inboundMessageId,t.selfPrincipalId),S=a.messages.startStream({conversationId:t.channelId,clientMessageId:$,idempotencyKey:$,replyTo:e.inReplyTo??t.inboundMessageId,type:"agent_reply",content:{}});let v,q=!1;try{q="duplicate"===(await S.opened()).outcome}catch(n){let s="unconfirmed";try{s=(await a.messages.getEnvelope(t.channelId,$)).state}catch{}return r.warn?.(`[im] runtime reply stream open outcome unknown requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} reply_id=${$} reconciled_state=${s}: ${formatErrMessage(n)}; runtime_not_invoked`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_open_outcome_unknown",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(g),!1}if(q)return r.info?.(`[im] runtime dispatch skipped existing deterministic reply requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"idempotent_reply_exists",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(g),!1;const k=new SerializedCallbackQueue,C=createImReplySink({stream:S,openReasoningStream:()=>{const n=deterministicReplyMessageId("rsn",t.channelId,t.inboundMessageId,t.selfPrincipalId);return v=a.messages.startStream({conversationId:t.channelId,clientMessageId:n,idempotencyKey:n,replyTo:e.inReplyTo??t.inboundMessageId,type:"agent_reasoning",content:{}}),v},abortSignal:y.signal,callbackQueue:k,logger:r,requestId:d,runId:l,channelId:t.channelId,inboundMessageId:t.inboundMessageId,origin:m,onAgentRunStart:e=>{s.bindRun(_,e)}}),O=interceptAgentRuntimeFailures(C,"im_runtime",e=>r.warn?.(`[im] runtime failure reply suppressed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} error_code=${e.errorCode}`)),T=O.sink,A=async(e,n)=>{if(!v)return!1;try{return"cancel"===e?await v.cancel({stopReason:"user_stop"}):"fail"===e?await v.fail(n?{stopReason:n}:void 0):await v.finalize({stopReason:"end_turn"}),!1}catch(e){return r.warn?.(`[im] runtime reasoning stream terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`),!0}};let D,U,K=!1;const N=async e=>{T.dispatcher.markComplete(e);try{await T.dispatcher.waitForIdle()}catch{}},F=()=>{b();const n=normalizePromptTimeoutMs(e.abortTerminalGraceMs)??1e4;w=setTimeout(()=>{w=void 0,(async()=>{if(S.isTerminated)return;const e=y.signal.reason??f,n=void 0!==U,a=!n&&"string"==typeof e&&e.startsWith("chat_cancel");if(await N("failure"),!S.isTerminated)try{a?(await A("cancel"),await S.cancel({stopReason:"user_stop"})):n?(await A("fail","timeout"),await failMessageStream(S,{body:U?.message??"IM runtime dispatch timed out",stopReason:"timeout"})):(await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"})),r.warn?.(`[im] abort settlement watchdog terminalized reply requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} user_cancel=${a} timeout=${n}`)}catch(e){r.warn?.(`[im] abort settlement watchdog terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`)}})()},n)};y.signal.aborted?F():y.signal.addEventListener("abort",F,{once:!0});const L=()=>{if(!y.signal.aborted)return;const e=y.signal.reason;throw e instanceof Error?e:new Error(String(e??"runtime dispatch aborted"))};let Q,P,Y;const G=async e=>{if(!_)throw new Error("session_mutation_not_acquired");const n=await s.converge(_,e);return n.ok?(Q=n.permit,!0):(r.error?.(`[im] session mutation recovery required requestId=${d} sessionKey=${h} reason=${n.recovery.reason}`),P={code:n.recovery.code,reason:n.recovery.reason,sessionKey:n.recovery.sessionKey,operationId:n.recovery.operationId,surface:"message_service",...n.recovery.terminalKind?{terminalKind:n.recovery.terminalKind}:{}},!1)};try{L();const o=s.tryStartInteractive({sessionKey:h,label:"im_chat_message"},"message_service");if(!o.ok){try{await failMessageStream(S,{body:SESSION_BUSY_PUBLIC_MESSAGE,stopReason:"error"})}catch(e){r.warn?.(`[im] session busy terminal write failed channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return r.warn?.(`[im] session write gate busy sessionKey=${h} channel=${t.channelId} in_reply_to=${t.inboundMessageId} active_operation=${o.activeOperationId??"unknown"}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"session_busy",cause:"runtime_error"}),!1}if(_=o.token,s.bindRun(_,l),c){const e=s.snapshot(h);Y={runtimeConversationUid:t.channelId,runtimeSessionKey:t.channelId,historyGeneration:c.historyGeneration(t.channelId),targetMessageId:t.inboundMessageId,runtimeRunId:l,activeRunRegistrationId:`im:${e?.operationId??l}`},c.activeRuns.register(Y,async()=>{const e=`chat_cancel:activeRun:${Y.activeRunRegistrationId}`,n=await i.abort(g,e);return n.ok||"already_aborted"===n.reason?"accepted":"terminal_committing"===n.reason||"not_found"===n.reason?"already_terminal":"recovery_required"})}L(),r.info?.(`[im] runtime dispatching requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} origin=${m}`),e.onLifecycleEvent?.({type:"dispatch_start",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId});const y=dispatchRuntimeReplyWithRoute({runtime:n,pluginConfig:e.pluginConfig,preparedPrompt:t.dispatchInput,replyRoute:p,replySinks:{im:T},onDispatchInvoked:()=>{K=!0}});void 0!==I&&(D=setTimeout(()=>{U=new Error(`IM runtime dispatch timed out after ${I}ms`),r.warn?.(`[im] runtime dispatch timeout requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} timeout_ms=${I}`),M(U)},I));const f=Promise.race([y,E]),R=await f.finally(()=>{D&&(clearTimeout(D),D=void 0)}),w=O.detectedFailure();if(w)throw new Error(w.diagnosticMessage);if(L(),await N("success"),L(),!i.claimTerminal(g,"completion")){if(L(),"cancelling"===i.phase(g))throw new Error("cancellation in progress");throw new Error("terminal outcome already claimed")}if(!await G("completed"))return await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:"runtime_error",...P?{recovery:P}:{}}),!1;const b=await A("complete");let $=!1;try{await S.finalize({stopReason:"end_turn"})}catch(e){$=!0,r.warn?.(`[im] runtime reply stream finalize outcome unknown requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}c&&Y&&c.activeRuns.markTerminal(Y,"completed"),r.info?.(`[im] runtime dispatch completed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(R.counts)} elapsed_ms=${Date.now()-u}`),e.onLifecycleEvent?.({type:"dispatch_complete",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,counts:R.counts,...$?{terminalWriteFailed:!0}:{},...b?{reasoningTerminalWriteFailed:!0}:{}});const v="string"==typeof t.dispatchInput.message?t.dispatchInput.message:"",q=T.dispatcher.getAccumulatedBody?.()??"";return maybeRenameImConversationTitle({client:a,conversationId:t.channelId,question:v,answer:q,logger:r}),!0}catch(n){await N("failure");const a=y.signal.aborted,s=a?y.signal.reason:void 0,o="cancelling"===i.phase(g),l=void 0!==U,u=s??f,m=!l&&"string"==typeof u&&u.startsWith("chat_cancel");let p="runtime_error";l?p="timeout":(a||o)&&(p="abort");const I=o||m||l||a?"cancellation":"completion";if(!i.claimTerminal(g,I)){if(r.warn?.(`[im] terminal claim raced requestId=${d} channel=${t.channelId}; best-effort envelope terminal if still streaming`),!S.isTerminated)try{m?(await A("cancel"),await S.cancel({stopReason:"user_stop"})):l?(await A("fail","timeout"),await failMessageStream(S,{body:U?.message??formatErrMessage(n),stopReason:"timeout"})):(await A("fail"),await failMessageStream(S,{body:formatErrMessage(n),stopReason:"error"}))}catch(e){r.warn?.(`[im] raced terminal write failed requestId=${d}: ${formatErrMessage(e)}`)}throw n}const h=m?"cancelled":l?"timeout":"error";if(c&&Y&&c.activeRuns.markTerminal(Y,"cancelled"===h?"cancelled":"failed"),_&&!await G(h))throw m?(await A("cancel"),await S.cancel({stopReason:"user_stop"}).catch(()=>{})):(await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{})),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:p,...P?{recovery:P}:{}}),n;const R=classifyAgentRuntimeFailure({raw:n,source:"im_runtime",runtimeKind:"openclaw",requestAborted:m}),E=m?R:O.detectedFailure()??R;m?await A("cancel"):l?await A("fail","timeout"):await A("fail");try{m?await S.cancel({stopReason:"user_stop"}):await failMessageStream(S,{body:E.publicMessage,stopReason:messageStopReasonForRuntimeFailure(E)})}catch(e){r.warn?.(`[im] runtime stream terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`)}throw r.warn?.(`[im] runtime dispatch failed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} cause=${p} dispatchInvoked=${K} error_code=${E.errorCode}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:E.errorCode,cause:p}),n}finally{b(),i.unregister(g),K?Q&&s.release(Q):s.releaseBeforeRuntime(_)}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{appendFile,mkdir,readFile}from"node:fs/promises";import{dirname}from"node:path";export class FileInboundExecutionJournal{path;claimed=new Set;loaded;writes=Promise.resolve();constructor(e){this.path=e}async claim(e){if(!e)throw new Error("inbound execution command key is required");await(this.loaded??=this.load());const i=this.writes.then(async()=>this.claimed.has(e)?"existing":(await mkdir(dirname(this.path),{recursive:!0,mode:448}),await appendFile(this.path,`${JSON.stringify({version:1,commandKey:e})}\n`,{encoding:"utf8",mode:384}),this.claimed.add(e),"acquired"));return this.writes=i.then(()=>{},()=>{}),await i}async load(){let e;try{e=await readFile(this.path,"utf8")}catch(e){if(isNodeError(e)&&"ENOENT"===e.code)return;throw e}const i=e.split("\n");""===i.at(-1)&&i.pop();for(const e of i){const i=JSON.parse(e);if(1!==i.version||"string"!=typeof i.commandKey||0===i.commandKey.length)throw new Error("inbound execution journal is corrupt");this.claimed.add(i.commandKey)}}}function isNodeError(e){return e instanceof Error&&"code"in e}
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{MessageClient}from"@beeos-ai/message-sdk";import{hasRuntimeReplyApi}from"./runtime/reply-router.js";import{PromptRunStore}from"./runtime/prompt-run-store.js";import{SessionWriteGate}from"./acp-gateway/session-write-gate.js";import{OpenClawSessionWriteQuiescence}from"./runtime/openclaw-session-write-quiescence.js";import{SessionMutationCoordinator}from"./runtime/session-mutation-coordinator.js";import{scheduleProcessRuntimeRestart}from"./runtime/runtime-restart-scheduler.js";import{createImInboundHandler}from"./im-inbound-handler.js";import{MessageServiceRecoveryScanner}from"./im-recovery-scanner.js";import{createTokenProvider}from"@beeos-ai/message-sdk/auth";import{registerA2ATools,TaskHandleRegistry}from"./a2a/tools.js";import{BEEOS_UPLOAD_TOOL_NAME,BEEOS_UPLOAD_TOOL_SCHEMA,BEEOS_UPLOAD_TOOL_DESCRIPTION,executeUpload}from"./upload/tool.js";import{BEEOS_LIST_FILES_TOOL_NAME,BEEOS_LIST_FILES_TOOL_SCHEMA,BEEOS_LIST_FILES_TOOL_DESCRIPTION,executeListFiles}from"./files/list-tool.js";import{BEEOS_FETCH_FILE_TOOL_NAME,BEEOS_FETCH_FILE_TOOL_SCHEMA,BEEOS_FETCH_FILE_TOOL_DESCRIPTION,executeFetchFile}from"./files/fetch-tool.js";import{writeUploadToolResult,cleanupUploadToolResultCache}from"./upload/result-cache.js";import{handleFileStage}from"./upload/file-stage.js";import{handleStatusRequest}from"./status.js";import{ACPServer}from"./acp-server.js";import{createObsEventWriter}from"./observability.js";import{resolvePluginVersion}from"./plugin-version.js";import{createTerminalWebSocketService}from"./terminal/websocket.js";import{TerminalManager}from"./terminal/session-manager.js";import{loadAgentKeyPair,agentBoundFetch,agentFetch}from"./agent-auth.js";import{readDiscoveryCache,writeDiscoveryCache}from"./discovery-cache.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{setUploadDeps}from"./upload/upload-file.js";import{registerLocalFileExtractor}from"./upload/local-file-extractors.js";import{browserScreenshotExtractor}from"./browser/screenshot-extractor.js";import{BEEOS_FILE_RESOURCE_ROUTING_RULE}from"./files/beeos-file-uri.js";import{CanvasStateManager}from"./canvas/state.js";import{CanvasRelayClient}from"./canvas/relay-client.js";import{RENDER_UI_TOOL_NAME,RENDER_UI_TOOL_DESCRIPTION,RENDER_UI_TOOL_SCHEMA,CLOSE_UI_TOOL_NAME,CLOSE_UI_TOOL_DESCRIPTION,CLOSE_UI_TOOL_SCHEMA,createRenderUiHandler,createCloseUiHandler,CANVAS_SYSTEM_PROMPT}from"./canvas/tools.js";import{FormSchemaRegistry}from"./surface/form-schema-registry.js";import{buildCronSchemas}from"./surface/cron-schemas.js";import{RENDER_FORM_TOOL_NAME,RENDER_FORM_TOOL_SCHEMA,buildRenderFormDescription,createRenderFormHandler}from"./surface/render-form-tool.js";import{ResourceDescriptorRegistry}from"./surface/resource-descriptor-registry.js";import{CRON_RESOURCE}from"./surface/cron-resource.js";import{generateActionTools}from"./surface/action-tool-generator.js";import{buildThirdPartyChannelSet,isThirdPartyChannel}from"./channel-detection.js";import{registerArouterMediaProviders}from"./media/index.js";import{homedir}from"node:os";import{join}from"node:path";import{RuntimeProductionAuthority}from"./runtime/runtime-production-authority.js";import{FileRuntimeCommandJournal}from"./runtime/runtime-command-journal.js";import{FileRuntimePoisonDeliveryJournal}from"./runtime/runtime-poison-delivery-journal.js";import{InProcessMcpToolBridge}from"./acp-gateway/mcp-tool-bridge.js";import{LocalEncryptedCredentialReferenceVault}from"./acp-gateway/mcp-credential-reference-port.js";import{AgentGatewayGrantJwksReadinessAdapter,RuntimeServiceTransportReadinessAdapter,createRuntimeServiceProductionCandidate}from"./runtime/runtime-service-production-candidate.js";import{MessageServiceRuntimeAuthenticatedFetch}from"./runtime/message-service-runtime-authenticated-fetch.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null,sharedCanvasState=null,sharedAcpServer=null;const sharedSessionWriteGate=new SessionWriteGate,sharedPromptRunStore=new PromptRunStore;let sharedSessionMutationCoordinator,sharedRuntimeAuthority,sharedMcpToolBridge,sharedMcpCredentialVault;const sharedTerminalManager=new TerminalManager;let _initializing=!1,_pluginRegisteredAt=0;const ED25519_PKCS8_PREFIX=Buffer.from("302e020100300506032b657004220420","hex");function agentKeyPairToIdentity(e){return{publicKeyBase64:()=>Buffer.from(e.publicKey).toString("base64"),sign:async r=>{const{createPrivateKey:o,sign:t}=await import("node:crypto"),a=o({key:32===e.privateKey.length?Buffer.concat([ED25519_PKCS8_PREFIX,Buffer.from(e.privateKey)]):Buffer.from(e.privateKey),format:"der",type:"pkcs8"});return new Uint8Array(t(null,Buffer.from(r),a))}}}let unhandledRejectionObserverInstalled=!1;function installUnhandledRejectionObserver(e,r,o){unhandledRejectionObserverInstalled||(unhandledRejectionObserverInstalled=!0,process.on("unhandledRejection",o=>{const t=o instanceof Error?o.message:String(o),a=o instanceof Error?o.stack:void 0;e.logger.error?.(`[beeos-claw] unhandledRejection: ${t}${a?`\n${a}`:""}`),r({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:t,error:{code:"unhandled_rejection",message:t,...a?{stack:a}:{}}})}),process.on("uncaughtException",o=>{const t=o instanceof Error?o.message:String(o);e.logger.error?.("[beeos-claw] uncaughtException:",o),r({component:"plugin",domain:"fatal",name:"uncaught_exception",severity:"error",summary:t,error:{code:"uncaught_exception",message:t}})}))}function initializePlugin(e){let r,o;try{r=e.runtime?.config?.loadConfig?.()}catch{}try{o=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:r})}catch(r){return e.logger.error?.("[beeos-claw] Failed to resolve config:",r),null}const t=o.config,a=buildThirdPartyChannelSet(r);a.size>0&&e.logger.info?.(`[beeos-claw] Third-party channels detected: ${[...a].join(", ")}`);const n=t.log.enabled?createObsEventWriter({dir:t.log.dir,redaction:{gatewayToken:t.gateway.token},logger:e.logger}):()=>{};n({component:"plugin",domain:"lifecycle",name:"lifecycle.startup",severity:"info",payload:{pluginVersion:PLUGIN_VERSION,pid:process.pid,nodeVersion:process.version,cwd:process.cwd()}}),n({component:"plugin",domain:"config",name:"config.resolved",severity:"info",payload:{config:t,sources:o.sources,openclawConfigPath:o.openclawConfigPath}});for(const r of o.validation)n({component:"plugin",domain:"config",name:"config.validation",severity:r.severity,summary:r.message,error:{code:r.code,message:r.message,nextSteps:r.nextSteps}}),"error"===r.severity?e.logger.error?.(`[beeos-claw] ${r.message}`):e.logger.warn?.(`[beeos-claw] ${r.message}`);if(o.validation.some(e=>"error"===e.severity)&&e.logger.error?.("[beeos-claw] Plugin has configuration errors — some features may not work."),t.keyFile)try{agentKeys=loadAgentKeyPair(t.keyFile);const r=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${r})`)}catch(r){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${r}`),e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no key pair — signed requests disabled)`)}else e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no keyFile configured)`);return agentKeys&&t.agentGatewayUrl&&(setUploadDeps({gatewayUrl:t.agentGatewayUrl,keys:agentKeys,logger:e.logger}),registerLocalFileExtractor(browserScreenshotExtractor),e.logger.info?.("[beeos-claw] Upload deps + browser screenshot extractor registered")),{cfg:t,writeObs:n,thirdPartyChannels:a,openclawConfig:r}}function registerUploadTools(e,r,o){const{cfg:t,writeObs:a}=r;cleanupUploadToolResultCache().catch(r=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(r)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(r,o,n){e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} params=${JSON.stringify(o)}`);const s=await executeUpload(o,t,agentKeys,n);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} ok=${s.ok}`),a({component:"upload-tool",domain:"tool",name:s.ok?"upload.success":"upload.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,fileCount:s.files?.length??0,error:s.error}}),(async o=>{try{await writeUploadToolResult(r,BEEOS_UPLOAD_TOOL_NAME,o),await cleanupUploadToolResultCache()}catch(o){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${r}: ${String(o)}`)}return o})(s)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,o){await handleFileStage(e,o,r.cfg)}}),e.registerHttpRoute({path:"/beeos/status",auth:"plugin",handler(e,r){handleStatusRequest(e,r,{publicKey:agentKeys?.publicKey??null,pluginVersion:PLUGIN_VERSION})}}),e.registerTool({name:BEEOS_LIST_FILES_TOOL_NAME,description:BEEOS_LIST_FILES_TOOL_DESCRIPTION,parameters:BEEOS_LIST_FILES_TOOL_SCHEMA,async execute(r,o,n){e.logger.debug?.(`[beeos_list_files] toolCallId=${r} params=${JSON.stringify(o)}`);const s=await executeListFiles(o,t,agentKeys,n);return a({component:"file-list-tool",domain:"tool",name:s.ok?"list_files.success":"list_files.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,fileCount:s.files?.length??0,error:s.error}}),s}}),e.registerTool({name:BEEOS_FETCH_FILE_TOOL_NAME,description:BEEOS_FETCH_FILE_TOOL_DESCRIPTION,parameters:BEEOS_FETCH_FILE_TOOL_SCHEMA,async execute(r,o,n){e.logger.debug?.(`[beeos_fetch_file] toolCallId=${r} params=${JSON.stringify(o)}`);const s=await executeFetchFile(o,t,agentKeys,{signal:n,resolvePath:e.resolvePath});return a({component:"file-fetch-tool",domain:"tool",name:s.ok?"fetch_file.success":"fetch_file.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,path:s.path,size:s.size,error:s.error}}),s}})}function registerServices(e,r,o){const{cfg:t,writeObs:a,thirdPartyChannels:n}=r,s=agentKeys?(e,r)=>agentFetch(e,agentKeys,r):void 0,i=agentKeys?(e,r)=>agentBoundFetch(e,agentKeys,r):void 0,l=agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):void 0,c=join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos");sharedMcpToolBridge??=new InProcessMcpToolBridge,!sharedMcpCredentialVault&&agentKeys&&l&&(sharedMcpCredentialVault=new LocalEncryptedCredentialReferenceVault(join(c,"mcp-credential-vault.json"),l,agentKeys.privateKey)),sharedMcpToolBridge.registerTools(e),!sharedRuntimeAuthority&&agentKeys&&t.agentGatewayUrl&&s&&i&&(sharedRuntimeAuthority=new RuntimeProductionAuthority({stateDirectory:c,agentGatewayUrl:t.agentGatewayUrl,agentFetch:s,boundAgentFetch:i,keys:agentKeys,logger:e.logger})),sharedSessionMutationCoordinator??=new SessionMutationCoordinator(sharedSessionWriteGate,new OpenClawSessionWriteQuiescence(e.logger,{agentId:t.gateway.agentId||"main"}),e.logger,{onRecoveryRequired:r=>{scheduleProcessRuntimeRestart(r,e.logger)}});const g=sharedSessionMutationCoordinator;sharedAcpServer||(sharedAcpServer=new ACPServer(t,e.logger,g,a,s,l,n,sharedTerminalManager,sharedRuntimeAuthority,void 0,sharedMcpToolBridge,sharedMcpCredentialVault,i));const d=sharedAcpServer;o.current=d;const u=sharedPromptRunStore,{enabled:p,url:m,principalId:f,a2aEnabled:y}=t.messageBus;let w;t.agentGatewayUrl&&agentKeys&&(w=createTokenProvider({agentGatewayUrl:t.agentGatewayUrl,identity:agentKeyPairToIdentity(agentKeys)}));const h=w?new MessageServiceRuntimeAuthenticatedFetch({tokenProvider:w,...t.messageBus.serviceUrl?{configuredServiceUrl:t.messageBus.serviceUrl}:{}}):void 0;e.registerService({id:"beeos-acp",async start(){const r=Date.now(),o=_pluginRegisteredAt?r-_pluginRegisteredAt:-1;e.logger.info?.(`[beeos-claw] beeos-acp service start() begin, sincePluginRegister=${o}ms, bridge.url=${t.bridge.url||"(empty)"}`);const a=await createRuntimeServiceProductionCandidate({...t.agentGatewayUrl&&s?{authorizationAdapter:new AgentGatewayGrantJwksReadinessAdapter({agentGatewayUrl:t.agentGatewayUrl})}:{},...h&&t.agentGatewayUrl&&i&&process.env.MESSAGE_RUNTIME_DELIVERY_KEY?.trim()?{transportAdapter:new RuntimeServiceTransportReadinessAdapter({agentGatewayUrl:t.agentGatewayUrl,messageService:h,boundAgentFetch:i,scopedDeliveryKey:process.env.MESSAGE_RUNTIME_DELIVERY_KEY.trim()})}:{},commandJournal:sharedRuntimeAuthority?.journal??new FileRuntimeCommandJournal(join(c,"runtime-command-journal.json")),poisonJournal:new FileRuntimePoisonDeliveryJournal(join(c,"runtime-poison-deliveries.json")),onConsumerDeliveryError:r=>{const o=r instanceof Error?`${r.name}: ${r.message}${"status"in r?` status=${r.status}`:""}${"code"in r?` code=${r.code}`:""}\n${r.stack??""}`:String(r);e.logger.error?.(`[runtime-service] delivery worker failed before acknowledgement; delivery retained for retry: ${o}`)},onConsumerRequestFailure:r=>e.logger.error?.(`[runtime-service] request failed operationId=${r.operationId} method=${r.method}: ${r.error instanceof Error?r.error.stack??r.error.message:String(r.error)}`)});if(sharedRuntimeAuthority?.setRuntimeServiceMethods("ready"===a.status?a.serviceMethods:[]),sharedRuntimeAuthority?.setRuntimeResourceBindingContractRevision("ready"===a.status?a.resourceBindingContractRevision:void 0),d.setRuntimeRpcConsumerFactory("ready"===a.status?a.factory:void 0),"unavailable"===a.status?e.logger.warn?.("[runtime-service] capability fenced: %s",a.blockers.map(e=>e.id).join(",")):a.capabilityBlockers.length&&e.logger.warn?.("[runtime-service] method capability fenced: %s",a.capabilityBlockers.map(e=>e.method).join(",")),!t.bridge.url&&t.agentGatewayUrl&&s){const r=Date.now(),o=await discoverBridgeFromGW(t.agentGatewayUrl,s);if(e.logger.info?.(`[beeos-claw] bridge discovery took ${Date.now()-r}ms result=${o?.url??"null"}`),o)t.bridge.url=o.url,writeDiscoveryCache({bridge:{url:o.url,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${o.url}`);else{const r=readDiscoveryCache();r?.bridge?.url?(t.bridge.url=r.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${r.bridge.url}`)):e.logger.debug?.("[beeos-claw] Bridge URL not available (no GW, no cache) — bridge disabled")}}await d.start(t.bridge.url||void 0),e.logger.info?.(`[beeos-claw] ACP Server started (total service start: ${Date.now()-r}ms)`)},async stop(){await d.stop(),e.logger.info?.("[beeos-claw] ACP Server stopped")}});const b=p&&(w||t.messageBus.serviceUrl),_=b?d.getMessageClient():null;if(b&&_){_.on("disconnect",r=>{e.logger.warn?.("[beeos-claw] message bus disconnected (code=%s, reason=%s) — SDK will auto-reconnect",r?.code??"?",r?.reason??"?")}),_.on("error",r=>{e.logger.warn?.(`[beeos-claw] message bus error: ${r instanceof Error?r.message:String(r)}`)});let r=f||process.env.BEEOS_PRINCIPAL_ID||"";const o={serviceUrl:t.messageBus.serviceUrl||"https://msg.invalid",centrifugoUrl:m,tokenProvider:w,logger:e.logger},n=new Map;let s,i=!1;const l=async()=>{if(r||!w)return r;try{const o=await w("");r=o.identityId||"",r&&e.logger.info?.("[beeos-claw] Message bus principal resolved from Agent Gateway token: %s",r)}catch(r){e.logger.warn?.("[beeos-claw] Failed to resolve message bus principal from Agent Gateway token: %s",r instanceof Error?r.message:String(r))}return r},c=()=>{if(r)return[{localAgentId:d.getLocalAgentId(),principalId:r}];const e=d.getPlatformAgentMappings().map(({localAgentId:e,platformAgent:r})=>({localAgentId:e,principalId:r.instanceId||""})).filter(e=>e.principalId);if(e.length>0)return e;const o=d.getDefaultPlatformAgent()?.instanceId||"";return o?[{localAgentId:d.getLocalAgentId(),principalId:o}]:[]},p=e=>({getLocalAgentId:()=>e.localAgentId,resolveLocalAgentId:e=>d.resolveLocalAgentIdForPlatformAgent(e),getDefaultPlatformAgent:()=>({instanceId:e.principalId}),ensureImInboundSession:(e,r,o)=>{d.ensureImInboundSession(e,r,o)},chatAbort:(e,r)=>d.chatAbort(e,r)}),y=async(r,s)=>{if(n.has(r.principalId))return;const i=s?_:new MessageClient(o);s&&d.setMessageClient(o,r.principalId);const l=createImInboundHandler({runtime:e.runtime,pluginConfig:e.pluginConfig,client:i,localServer:p(r),promptRunStore:u,sessionMutationCoordinator:g,logger:e.logger,writeObs:a,principalIdOverride:r.principalId,promptTimeoutMs:t.promptTimeoutMs,activeRunAuthority:d.getActiveRunAuthority()}),c=new MessageServiceRecoveryScanner({client:i,imInbound:l,logger:e.logger});i.on("connect",()=>{c.trigger("reconnect"),e.logger.info?.("[beeos-claw] MessageClient connected (localAgentId=%s, principalId=%s, via=%s)",r.localAgentId,r.principalId,w?"gateway":"direct")}),i.on("message",r=>{(async()=>{"chat_message"===r.type?await l.onChatMessage(r):"chat_cancel"===r.type&&await l.onChatCancel(r)})().catch(r=>{e.logger.warn?.(`[beeos-claw] message handler failed: ${r instanceof Error?r.message:String(r)}`)})}),n.set(r.principalId,{client:i,scanner:c,primary:s});try{await i.connect({identityId:r.principalId}),c.start(r.principalId)}catch(e){throw n.delete(r.principalId),c.stop(),s||await i.disconnect().catch(()=>{}),e}},h=async r=>{if(i)if(s)await s;else{s=(async()=>{const o=c();if(0===o.length)return void e.logger.warn?.("[beeos-claw] No principalId resolved, message bus disabled");const t=new Set(o.map(e=>e.principalId));let a=!1;for(const[o,s]of n)t.has(o)||(s.primary&&(a=!0),s.scanner.stop(),await s.client.disconnect(),n.delete(o),e.logger.info?.("[beeos-claw] MessageClient disconnected stale identity principalId=%s reason=%s",o,r));if(a&&n.size>0)for(const[o,t]of n)t.scanner.stop(),await t.client.disconnect(),n.delete(o),e.logger.info?.("[beeos-claw] MessageClient disconnected for primary rebalance principalId=%s reason=%s",o,r);let s=[...n.values()].some(e=>e.primary);for(const e of o){const r=!s;await y(e,r),r&&(s=!0)}})();try{await s}finally{s=void 0}}};e.registerService({id:"beeos-message-bus",async start(){if(i=!0,d.onAgentMappingUpdated(()=>{h("agent-mapping-updated").catch(r=>{e.logger.error?.("[beeos-claw] Message bus mapping sync failed:",r)})}),await l(),!r)try{await d.waitForAgentMapping()}catch(r){e.logger.warn?.("[beeos-claw] Failed to resolve principalId from agent sync:",r)}await h("startup")},async stop(){i=!1;const r=[...n.values()];n.clear();for(const e of r)e.scanner.stop(),await e.client.disconnect();e.logger.info?.("[beeos-claw] MessageClient disconnected")}})}else p?e.logger.warn?.("[beeos-claw] Message bus enabled but MESSAGE_SERVICE_URL not set — disabled"):e.logger.info?.("[beeos-claw] Message bus disabled (enabled=%s)",p);if(!1!==y&&t.agentGatewayUrl){const r=e=>d.getGatewaySessionKeyForToolCall(e),o=e=>d.getMentionsForToolCall(e),a=(e,r)=>{d.broadcastNotification(e,r)},n=new TaskHandleRegistry;registerA2ATools(e,{agentGatewayUrl:t.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},n,r,o,a,()=>d?.getDefaultPlatformAgent()?.instanceId),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(t.canvasEnabled){sharedCanvasState||(sharedCanvasState=new CanvasStateManager);const r=sharedCanvasState;if(t.agentGatewayUrl&&agentKeys){let o;r.setLogger(e.logger);{const e=t.agentGatewayUrl,r=agentKeys;o=async()=>{const o=await agentFetch(`${e}/api/v1/canvas/token`,r,{method:"POST"});if(!o.ok){const e=await o.text().catch(()=>"");throw new Error(`Canvas token returned ${o.status}: ${e}`)}return o.json()}}const a=a=>{const n=new CanvasRelayClient({canvasRelayUrl:t.canvasRelayUrl,agentKeys:agentKeys,agentId:a.id,displayName:a.displayName,avatarUrl:a.avatarUrl,tokenProvider:o,onReconnect:()=>d.onRelayReconnected(),logger:e.logger});n.connect(),r.setRelayClient(n),r.setAgentId(a.id),d.wireRelayActionHandler(),d.flushPendingCanvasLinks(),e.logger.info?.(`[beeos-claw] Canvas Relay multiplexed client connected (agentId=${a.id}, name=${a.name??"none"})`)};e.registerService({id:"beeos-canvas-relay",async start(){try{await d.waitForAgentMapping();const r=d.getDefaultPlatformAgent();if(r?.id)return void a(r);e.logger.info?.("[beeos-claw] Canvas Relay deferred: waiting for late agent sync..."),d.onceAgentMappingReady(()=>{const r=d.getDefaultPlatformAgent();if(r?.id)try{a(r)}catch(r){e.logger.warn?.(`[beeos-claw] Deferred Canvas Relay start failed: ${r}`)}else e.logger.warn?.("[beeos-claw] Agent mapping ready but no platform agent; Canvas Relay stays disabled")})}catch(r){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${r}`)}},stop(){r.getRelayClient()?.disconnect()}}),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const o={stateManager:r,logger:e.logger,writeObs:e=>a(e),isCanvasActive:e=>!!e&&d.isCanvasActiveForGatewayKey(e),getActiveSessionId:e=>d.getSessionIdForToolCall(e),getPlatformAgentId:e=>d.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>d.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>d.waitForAgentMapping()},s=new FormSchemaRegistry,i=buildCronSchemas(n,{capMs:t.cronMaxDurationCapMs});s.register(i.create),s.register(i.update);const l=new ResourceDescriptorRegistry;l.register(CRON_RESOURCE);const c=e=>async(r,o,t)=>(d.preMapToolCallSession(r),e(r,o));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:c(createRenderUiHandler(o))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:c(createCloseUiHandler(o))}),d.setCanvasState(r,{schemaRegistry:s,descriptorRegistry:l});const g=d.getFormBuilder();g&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(s),parameters:RENDER_FORM_TOOL_SCHEMA,execute:c(createRenderFormHandler({registry:s,builder:g,logger:e.logger}))});const u=d.getResourceRenderer();if(u){generateActionTools(l,e,async(e,r)=>d.gatewayRequest(e,r),u,e.logger)}const p=`\n\nTool selection guidance:\n- browser tool: Use when the user wants to OPEN, VISIT, NAVIGATE to a webpage, take SCREENSHOTS, interact with elements (click, type), or visually inspect pages.\n- beeos_search: Use ONLY for text-based information retrieval (fact-finding, research queries). Never for page interaction or screenshots.\n- beeos_fetch: Use ONLY to extract article text from a known URL. Never for visual content or screenshots.\n- ${BEEOS_FILE_RESOURCE_ROUTING_RULE}\nWhen the user says "open", "visit", "go to", "screenshot", or mentions seeing/viewing a webpage, ALWAYS prefer the browser tool.`,m=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),f=256,y=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,r)=>{if(r?.sessionKey){if(y.size>=f&&!y.has(r.sessionKey)){const e=y.keys().next().value;void 0!==e&&y.delete(e)}y.set(r.sessionKey,r.channelId)}if(isThirdPartyChannel(n,{sessionKey:r?.sessionKey,channelId:r?.channelId}))return{appendSystemContext:'This conversation is in a third-party channel (Lark/Telegram/Discord/Slack); Canvas surfaces are not visible here. Do NOT use render_ui, close_ui, or render_form. Use built-in tools directly (e.g. the `cron` tool with `action:"add"` for scheduled tasks) and respond with plain text.'};if(!r?.sessionKey)return{appendSystemContext:p};return d.isCanvasActiveForGatewayKey(r.sessionKey)?{appendSystemContext:CANVAS_SYSTEM_PROMPT+p}:{appendSystemContext:"Canvas is closed by user. Do NOT use render_ui, close_ui, or render_form. Respond with plain text only."+p}}),e.logger.debug?.("[beeos-claw] Canvas system prompt hook registered: before_prompt_build")}catch{e.logger.debug?.("[beeos-claw] before_prompt_build hook not supported")}try{e.on("before_tool_call",(e,r)=>{if(r?.sessionKey&&d.setPendingToolCallSessionKey(r.sessionKey),!m.has(e.toolName))return;const o=r?.sessionKey?y.get(r.sessionKey):void 0;return isThirdPartyChannel(n,{sessionKey:r?.sessionKey,channelId:o})?{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."}:r?.sessionKey?d.isCanvasActiveForGatewayKey(r.sessionKey)?void 0:{block:!0,blockReason:"Canvas is closed by user. Respond with plain text instead."}:{block:!0,blockReason:"Canvas session unknown. Respond with plain text instead."}}),e.logger.debug?.("[beeos-claw] Canvas tool guard registered: before_tool_call")}catch{e.logger.debug?.("[beeos-claw] before_tool_call hook not supported")}}e.logger.info?.("[beeos-claw] Canvas tools registered (render_ui, close_ui, render_form + auto-action tools)")}else e.logger.info?.("[beeos-claw] Canvas disabled by config (canvasEnabled=false)");if(t.bridge.shell.enabled&&!0===t.legacyTerminalWsEnabled){const r=createTerminalWebSocketService({config:{port:t.terminalWsPort,token:agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):"",allowedOrigins:t.terminalWsAllowedOrigins},terminalMgr:sharedTerminalManager,logger:e.logger,writeObs:a});e.registerService({id:"beeos-terminal-ws",start(){r.start(),e.logger.info?.(`[beeos-claw] Terminal WS started on port ${t.terminalWsPort}`)},stop(){r.stop(),e.logger.info?.("[beeos-claw] Terminal WS stopped")}})}else t.bridge.shell.enabled?e.logger.info?.("[beeos-claw] Legacy static-token Terminal WS disabled (default)"):e.logger.info?.("[beeos-claw] Terminal/shell disabled by config (bridge.shell.enabled=false)");if(r.openclawConfig){const o=mirrorOpenClawConfig(r.openclawConfig);o.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${o.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${o.reason}`)}}const CAPABILITY_REGISTRATION_MODES=new Set(["full","discovery","tool-discovery"]),plugin={id:"beeos-claw",name:"beeos-claw",description:"BeeOS platform bridge plugin for OpenClaw. Provides file upload/download, ACP bridge, and observability.",version:PLUGIN_VERSION,register(e){if(!_initializing)if(!e.registrationMode||CAPABILITY_REGISTRATION_MODES.has(e.registrationMode)){_initializing=!0;try{if(!hasRuntimeReplyApi(e.runtime))throw e.logger.error?.("[beeos-claw] FATAL: OpenClaw runtime.channel.reply.dispatchReplyFromConfig not exposed. Upgrade OpenClaw or revert beeos-claw."),new Error("openclaw_runtime_reply_api_unavailable");const r=initializePlugin(e);if(!r)return;const o={current:null};installUnhandledRejectionObserver(e,r.writeObs,o),registerUploadTools(e,r,o),registerArouterMediaProviders(e),registerServices(e,r,o),_pluginRegisteredAt=Date.now(),e.logger.info?.("[beeos-claw] Plugin registered successfully")}finally{_initializing=!1}}else e.logger.debug?.(`[beeos-claw] Skipping registration (mode=${e.registrationMode})`)}};export default plugin;
|
|
1
|
+
import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{hasRuntimeReplyApi}from"./runtime/reply-router.js";import{PromptRunStore}from"./runtime/prompt-run-store.js";import{SessionWriteGate}from"./acp-gateway/session-write-gate.js";import{OpenClawSessionWriteQuiescence}from"./runtime/openclaw-session-write-quiescence.js";import{SessionMutationCoordinator}from"./runtime/session-mutation-coordinator.js";import{scheduleProcessRuntimeRestart}from"./runtime/runtime-restart-scheduler.js";import{createImInboundHandler}from"./im-inbound-handler.js";import{createTokenProvider}from"@beeos-ai/message-sdk/auth";import{BeeosClawMessageClientProcessOwner}from"./message-client-composition.js";import{MessageProjectionInboundConsumer}from"./message-projection-inbound-consumer.js";import{RuntimeAuthorityMessageBusLifecycle}from"./runtime/runtime-authority-message-bus-lifecycle.js";import{FileInboundExecutionJournal}from"./inbound-execution-journal.js";import{registerA2ATools,TaskHandleRegistry}from"./a2a/tools.js";import{BEEOS_UPLOAD_TOOL_NAME,BEEOS_UPLOAD_TOOL_SCHEMA,BEEOS_UPLOAD_TOOL_DESCRIPTION,executeUpload}from"./upload/tool.js";import{BEEOS_LIST_FILES_TOOL_NAME,BEEOS_LIST_FILES_TOOL_SCHEMA,BEEOS_LIST_FILES_TOOL_DESCRIPTION,executeListFiles}from"./files/list-tool.js";import{BEEOS_FETCH_FILE_TOOL_NAME,BEEOS_FETCH_FILE_TOOL_SCHEMA,BEEOS_FETCH_FILE_TOOL_DESCRIPTION,executeFetchFile}from"./files/fetch-tool.js";import{writeUploadToolResult,cleanupUploadToolResultCache}from"./upload/result-cache.js";import{handleFileStage}from"./upload/file-stage.js";import{handleStatusRequest}from"./status.js";import{ACPServer}from"./acp-server.js";import{createObsEventWriter}from"./observability.js";import{resolvePluginVersion}from"./plugin-version.js";import{createTerminalWebSocketService}from"./terminal/websocket.js";import{TerminalManager}from"./terminal/session-manager.js";import{loadAgentKeyPair,agentBoundFetch,agentFetch}from"./agent-auth.js";import{readDiscoveryCache,writeDiscoveryCache}from"./discovery-cache.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{setUploadDeps}from"./upload/upload-file.js";import{registerLocalFileExtractor}from"./upload/local-file-extractors.js";import{browserScreenshotExtractor}from"./browser/screenshot-extractor.js";import{BEEOS_FILE_RESOURCE_ROUTING_RULE}from"./files/beeos-file-uri.js";import{CanvasStateManager}from"./canvas/state.js";import{CanvasRelayClient}from"./canvas/relay-client.js";import{RENDER_UI_TOOL_NAME,RENDER_UI_TOOL_DESCRIPTION,RENDER_UI_TOOL_SCHEMA,CLOSE_UI_TOOL_NAME,CLOSE_UI_TOOL_DESCRIPTION,CLOSE_UI_TOOL_SCHEMA,createRenderUiHandler,createCloseUiHandler,CANVAS_SYSTEM_PROMPT}from"./canvas/tools.js";import{FormSchemaRegistry}from"./surface/form-schema-registry.js";import{buildCronSchemas}from"./surface/cron-schemas.js";import{RENDER_FORM_TOOL_NAME,RENDER_FORM_TOOL_SCHEMA,buildRenderFormDescription,createRenderFormHandler}from"./surface/render-form-tool.js";import{ResourceDescriptorRegistry}from"./surface/resource-descriptor-registry.js";import{CRON_RESOURCE}from"./surface/cron-resource.js";import{generateActionTools}from"./surface/action-tool-generator.js";import{buildThirdPartyChannelSet,isThirdPartyChannel}from"./channel-detection.js";import{registerArouterMediaProviders}from"./media/index.js";import{homedir}from"node:os";import{join}from"node:path";import{RuntimeProductionAuthority}from"./runtime/runtime-production-authority.js";import{FileRuntimeCommandJournal}from"./runtime/runtime-command-journal.js";import{FileRuntimePoisonDeliveryJournal}from"./runtime/runtime-poison-delivery-journal.js";import{InProcessMcpToolBridge}from"./acp-gateway/mcp-tool-bridge.js";import{LocalEncryptedCredentialReferenceVault}from"./acp-gateway/mcp-credential-reference-port.js";import{AgentGatewayGrantJwksReadinessAdapter,RuntimeServiceTransportReadinessAdapter,createRuntimeServiceProductionCandidate}from"./runtime/runtime-service-production-candidate.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null,sharedCanvasState=null,sharedAcpServer=null;const sharedSessionWriteGate=new SessionWriteGate,sharedPromptRunStore=new PromptRunStore;let sharedSessionMutationCoordinator,sharedRuntimeAuthority,sharedMcpToolBridge,sharedMcpCredentialVault;const sharedMessageClientOwner=new BeeosClawMessageClientProcessOwner;let sharedActiveMessageConsumer,sharedInboundExecutionJournal;const sharedTerminalManager=new TerminalManager;let _initializing=!1,_pluginRegisteredAt=0;const ED25519_PKCS8_PREFIX=Buffer.from("302e020100300506032b657004220420","hex");function agentKeyPairToIdentity(e){return{publicKeyBase64:()=>Buffer.from(e.publicKey).toString("base64"),sign:async r=>{const{createPrivateKey:o,sign:t}=await import("node:crypto"),n=o({key:32===e.privateKey.length?Buffer.concat([ED25519_PKCS8_PREFIX,Buffer.from(e.privateKey)]):Buffer.from(e.privateKey),format:"der",type:"pkcs8"});return new Uint8Array(t(null,Buffer.from(r),n))}}}let unhandledRejectionObserverInstalled=!1;function installUnhandledRejectionObserver(e,r,o){unhandledRejectionObserverInstalled||(unhandledRejectionObserverInstalled=!0,process.on("unhandledRejection",o=>{const t=o instanceof Error?o.message:String(o),n=o instanceof Error?o.stack:void 0;e.logger.error?.(`[beeos-claw] unhandledRejection: ${t}${n?`\n${n}`:""}`),r({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:t,error:{code:"unhandled_rejection",message:t,...n?{stack:n}:{}}})}),process.on("uncaughtException",o=>{const t=o instanceof Error?o.message:String(o);e.logger.error?.("[beeos-claw] uncaughtException:",o),r({component:"plugin",domain:"fatal",name:"uncaught_exception",severity:"error",summary:t,error:{code:"uncaught_exception",message:t}})}))}function initializePlugin(e){let r,o;try{r=e.runtime?.config?.loadConfig?.()}catch{}try{o=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:r})}catch(r){return e.logger.error?.("[beeos-claw] Failed to resolve config:",r),null}const t=o.config,n=buildThirdPartyChannelSet(r);n.size>0&&e.logger.info?.(`[beeos-claw] Third-party channels detected: ${[...n].join(", ")}`);const a=t.log.enabled?createObsEventWriter({dir:t.log.dir,redaction:{gatewayToken:t.gateway.token},logger:e.logger}):()=>{};a({component:"plugin",domain:"lifecycle",name:"lifecycle.startup",severity:"info",payload:{pluginVersion:PLUGIN_VERSION,pid:process.pid,nodeVersion:process.version,cwd:process.cwd()}}),a({component:"plugin",domain:"config",name:"config.resolved",severity:"info",payload:{config:t,sources:o.sources,openclawConfigPath:o.openclawConfigPath}});for(const r of o.validation)a({component:"plugin",domain:"config",name:"config.validation",severity:r.severity,summary:r.message,error:{code:r.code,message:r.message,nextSteps:r.nextSteps}}),"error"===r.severity?e.logger.error?.(`[beeos-claw] ${r.message}`):e.logger.warn?.(`[beeos-claw] ${r.message}`);if(o.validation.some(e=>"error"===e.severity)&&e.logger.error?.("[beeos-claw] Plugin has configuration errors — some features may not work."),t.keyFile)try{agentKeys=loadAgentKeyPair(t.keyFile);const r=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${r})`)}catch(r){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${r}`),e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no key pair — signed requests disabled)`)}else e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no keyFile configured)`);return agentKeys&&t.agentGatewayUrl&&(setUploadDeps({gatewayUrl:t.agentGatewayUrl,keys:agentKeys,logger:e.logger}),registerLocalFileExtractor(browserScreenshotExtractor),e.logger.info?.("[beeos-claw] Upload deps + browser screenshot extractor registered")),{cfg:t,writeObs:a,thirdPartyChannels:n,openclawConfig:r}}function registerUploadTools(e,r,o){const{cfg:t,writeObs:n}=r;cleanupUploadToolResultCache().catch(r=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(r)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(r,o,a){e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} params=${JSON.stringify(o)}`);const i=await executeUpload(o,t,agentKeys,a);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} ok=${i.ok}`),n({component:"upload-tool",domain:"tool",name:i.ok?"upload.success":"upload.failure",severity:i.ok?"info":"warn",payload:{toolCallId:r,ok:i.ok,fileCount:i.files?.length??0,error:i.error}}),(async o=>{try{await writeUploadToolResult(r,BEEOS_UPLOAD_TOOL_NAME,o),await cleanupUploadToolResultCache()}catch(o){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${r}: ${String(o)}`)}return o})(i)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,o){await handleFileStage(e,o,r.cfg)}}),e.registerHttpRoute({path:"/beeos/status",auth:"plugin",handler(e,r){handleStatusRequest(e,r,{publicKey:agentKeys?.publicKey??null,pluginVersion:PLUGIN_VERSION})}}),e.registerTool({name:BEEOS_LIST_FILES_TOOL_NAME,description:BEEOS_LIST_FILES_TOOL_DESCRIPTION,parameters:BEEOS_LIST_FILES_TOOL_SCHEMA,async execute(r,o,a){e.logger.debug?.(`[beeos_list_files] toolCallId=${r} params=${JSON.stringify(o)}`);const i=await executeListFiles(o,t,agentKeys,a);return n({component:"file-list-tool",domain:"tool",name:i.ok?"list_files.success":"list_files.failure",severity:i.ok?"info":"warn",payload:{toolCallId:r,ok:i.ok,fileCount:i.files?.length??0,error:i.error}}),i}}),e.registerTool({name:BEEOS_FETCH_FILE_TOOL_NAME,description:BEEOS_FETCH_FILE_TOOL_DESCRIPTION,parameters:BEEOS_FETCH_FILE_TOOL_SCHEMA,async execute(r,o,a){e.logger.debug?.(`[beeos_fetch_file] toolCallId=${r} params=${JSON.stringify(o)}`);const i=await executeFetchFile(o,t,agentKeys,{signal:a,resolvePath:e.resolvePath});return n({component:"file-fetch-tool",domain:"tool",name:i.ok?"fetch_file.success":"fetch_file.failure",severity:i.ok?"info":"warn",payload:{toolCallId:r,ok:i.ok,path:i.path,size:i.size,error:i.error}}),i}})}function registerServices(e,r,o){const{cfg:t,writeObs:n,thirdPartyChannels:a}=r,i=agentKeys?(e,r)=>agentFetch(e,agentKeys,r):void 0,s=agentKeys?(e,r)=>agentBoundFetch(e,agentKeys,r):void 0,l=agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):void 0,c=join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos"),g=sharedInboundExecutionJournal??=new FileInboundExecutionJournal(join(c,"inbound-execution-journal.v1.jsonl"));sharedMcpToolBridge??=new InProcessMcpToolBridge,!sharedMcpCredentialVault&&agentKeys&&l&&(sharedMcpCredentialVault=new LocalEncryptedCredentialReferenceVault(join(c,"mcp-credential-vault.json"),l,agentKeys.privateKey)),sharedMcpToolBridge.registerTools(e),!sharedRuntimeAuthority&&agentKeys&&t.agentGatewayUrl&&i&&s&&(sharedRuntimeAuthority=new RuntimeProductionAuthority({stateDirectory:c,agentGatewayUrl:t.agentGatewayUrl,agentFetch:i,boundAgentFetch:s,keys:agentKeys,logger:e.logger})),sharedSessionMutationCoordinator??=new SessionMutationCoordinator(sharedSessionWriteGate,new OpenClawSessionWriteQuiescence(e.logger,{agentId:t.gateway.agentId||"main"}),e.logger,{onRecoveryRequired:r=>{scheduleProcessRuntimeRestart(r,e.logger)}});const d=sharedSessionMutationCoordinator;sharedAcpServer||(sharedAcpServer=new ACPServer(t,e.logger,d,n,i,l,a,sharedTerminalManager,sharedRuntimeAuthority,void 0,sharedMcpToolBridge,sharedMcpCredentialVault,s));const u=sharedAcpServer;o.current=u;const p=sharedPromptRunStore,{enabled:m,principalId:y,a2aEnabled:f}=t.messageBus;let h;t.agentGatewayUrl&&agentKeys&&(h=createTokenProvider({agentGatewayUrl:t.agentGatewayUrl,identity:agentKeyPairToIdentity(agentKeys)}));const w=process.env.MESSAGE_RUNTIME_DELIVERY_KEY?.trim(),b=y||process.env.BEEOS_PRINCIPAL_ID||"",_=h?sharedMessageClientOwner.configure({tokenProvider:h,identityId:b,...t.messageBus.serviceUrl?{serviceUrl:t.messageBus.serviceUrl}:{},logger:e.logger,...sharedRuntimeAuthority&&w?{runtimeDelivery:{scopedDeliveryKey:w,authority:{currentLease:()=>{const e=sharedRuntimeAuthority?.authorityLease,r=sharedRuntimeAuthority?.currentConsumerBinding();return e&&r?{...e,journalStoreId:r.journalStoreId,journalGeneration:r.journalGeneration}:null}}}}:{}}):void 0;e.registerService({id:"beeos-acp",async start(){const r=Date.now(),o=_pluginRegisteredAt?r-_pluginRegisteredAt:-1;e.logger.info?.(`[beeos-claw] beeos-acp service start() begin, sincePluginRegister=${o}ms, bridge.url=${t.bridge.url||"(empty)"}`);const n=await createRuntimeServiceProductionCandidate({...t.agentGatewayUrl&&i?{authorizationAdapter:new AgentGatewayGrantJwksReadinessAdapter({agentGatewayUrl:t.agentGatewayUrl})}:{},..._&&t.agentGatewayUrl&&s&&w?{transportAdapter:new RuntimeServiceTransportReadinessAdapter({agentGatewayUrl:t.agentGatewayUrl,messageService:_.nodeTransport,messageClient:_.client,boundAgentFetch:s,scopedDeliveryKey:w})}:{},commandJournal:sharedRuntimeAuthority?.journal??new FileRuntimeCommandJournal(join(c,"runtime-command-journal.json")),poisonJournal:new FileRuntimePoisonDeliveryJournal(join(c,"runtime-poison-deliveries.json")),onConsumerDeliveryError:r=>{const o=r instanceof Error?`${r.name}: ${r.message}${"status"in r?` status=${r.status}`:""}${"code"in r?` code=${r.code}`:""}\n${r.stack??""}`:String(r);e.logger.error?.(`[runtime-service] delivery worker failed before acknowledgement; delivery retained for retry: ${o}`)},onConsumerRequestFailure:r=>e.logger.error?.(`[runtime-service] request failed operationId=${r.operationId} method=${r.method}: ${r.error instanceof Error?r.error.stack??r.error.message:String(r.error)}`)});if(sharedRuntimeAuthority?.setRuntimeServiceMethods("ready"===n.status?n.serviceMethods:[]),sharedRuntimeAuthority?.setRuntimeResourceBindingContractRevision("ready"===n.status?n.resourceBindingContractRevision:void 0),u.setRuntimeRpcConsumerFactory("ready"===n.status?n.factory:void 0),"unavailable"===n.status?e.logger.warn?.("[runtime-service] capability fenced: %s",n.blockers.map(e=>e.id).join(",")):n.capabilityBlockers.length&&e.logger.warn?.("[runtime-service] method capability fenced: %s",n.capabilityBlockers.map(e=>e.method).join(",")),!t.bridge.url&&t.agentGatewayUrl&&i){const r=Date.now(),o=await discoverBridgeFromGW(t.agentGatewayUrl,i);if(e.logger.info?.(`[beeos-claw] bridge discovery took ${Date.now()-r}ms result=${o?.url??"null"}`),o)t.bridge.url=o.url,writeDiscoveryCache({bridge:{url:o.url,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${o.url}`);else{const r=readDiscoveryCache();r?.bridge?.url?(t.bridge.url=r.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${r.bridge.url}`)):e.logger.debug?.("[beeos-claw] Bridge URL not available (no GW, no cache) — bridge disabled")}}await u.start(t.bridge.url||void 0),e.logger.info?.(`[beeos-claw] ACP Server started (total service start: ${Date.now()-r}ms)`)},async stop(){await u.stop(),e.logger.info?.("[beeos-claw] ACP Server stopped")}});if(m&&void 0!==h&&h){const r=sharedMessageClientOwner.createRegistrationToken(),o=async(e,r)=>{const o=await h(e);if(!o.identityId||o.identityId!==e)throw new Error("Message SDK token identity did not match the current runtime authority");return{principalId:e,runtimeEpoch:r}},a=(e,r)=>({getLocalAgentId:()=>r,getDefaultPlatformAgent:()=>({instanceId:e.principalId}),ensureImInboundSession:(e,r,o)=>{u.ensureImInboundSession(e,r,o)},chatAbort:(e,r)=>u.chatAbort(e,r)}),i=sharedRuntimeAuthority?new RuntimeAuthorityMessageBusLifecycle({authority:sharedRuntimeAuthority,logger:e.logger,bus:{connect:async i=>{await sharedMessageClientOwner.acquire(r,async r=>{const s=await o(i.instanceId,i.runtimeEpoch),l=r.client;u.setMessageClient(l,s.principalId);const c=new Map,m=new MessageProjectionInboundConsumer({client:l,bindings:()=>{const r=sharedRuntimeAuthority?.authorityLease;return r&&r.instanceId===s.principalId&&r.runtimeEpoch===s.runtimeEpoch?u.getPlatformAgentMappings().flatMap(({localAgentId:o,platformAgent:i})=>{if(!i.id||i.instanceId!==s.principalId)return[];let g=c.get(i.id);return g&&g.localAgentId===o||(g={localAgentId:o,inbound:createImInboundHandler({runtime:e.runtime,pluginConfig:e.pluginConfig,client:l,localServer:a(s,o),promptRunStore:p,sessionMutationCoordinator:d,logger:e.logger,writeObs:n,principalIdOverride:s.principalId,promptTimeoutMs:t.promptTimeoutMs,activeRunAuthority:u.getActiveRunAuthority()})},c.set(i.id,g)),[{principalId:s.principalId,targetAgentId:i.id,runtimeEpoch:r.runtimeEpoch,inbound:g.inbound}]}):[]},executionJournal:g,logger:e.logger});u.onAgentMappingUpdated(()=>{sharedActiveMessageConsumer===m&&m.refresh()});const y=sharedRuntimeAuthority?.subscribeAuthority(()=>{sharedActiveMessageConsumer===m&&m.refresh()})??(()=>{});let f=l.getSnapshot().connection;const h=l.subscribe(()=>{const r=l.getSnapshot();r.connection!==f&&(f=r.connection,"connected"===r.connection?e.logger.info?.("[beeos-claw] MessageClient connected (localAgentId=%s, principalId=%s)",u.getLocalAgentId(),s.principalId):"failed"===r.connection?e.logger.warn?.("[beeos-claw] MessageClient failed principalId=%s recoveryError=%s",s.principalId,r.recoveryError??"unknown"):"disconnected"!==r.connection&&"reconnecting"!==r.connection||e.logger.warn?.("[beeos-claw] MessageClient state=%s principalId=%s; SDK owns reconnect/recovery",r.connection,s.principalId))});sharedActiveMessageConsumer=m,m.start();try{await l.connect()}catch(e){throw sharedActiveMessageConsumer===m&&(sharedActiveMessageConsumer=void 0),await m.stop(),y(),h(),await l.disconnect().catch(()=>{}),e}return async()=>{sharedActiveMessageConsumer===m&&(sharedActiveMessageConsumer=void 0),await m.stop(),y(),h(),await l.disconnect(),e.logger.info?.("[beeos-claw] MessageClient disconnected")}})},disconnect:async()=>{await sharedMessageClientOwner.release(r)}}}):void 0;e.registerService({id:"beeos-message-bus",async start(){i?i.start():e.logger.warn?.("[beeos-claw] Message bus requires runtime authority; waiting is unavailable without Agent Gateway runtime identity")},async stop(){await(i?.stop())}})}else m?e.logger.warn?.("[beeos-claw] Message bus enabled but Agent Gateway token provider is unavailable — disabled"):e.logger.info?.("[beeos-claw] Message bus disabled (enabled=%s)",m);if(!1!==f&&t.agentGatewayUrl){const r=e=>u.getGatewaySessionKeyForToolCall(e),o=e=>u.getMentionsForToolCall(e),n=(e,r)=>{u.broadcastNotification(e,r)},a=new TaskHandleRegistry;registerA2ATools(e,{agentGatewayUrl:t.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},a,r,o,n,()=>u?.getDefaultPlatformAgent()?.instanceId),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(t.canvasEnabled){sharedCanvasState||(sharedCanvasState=new CanvasStateManager);const r=sharedCanvasState;if(t.agentGatewayUrl&&agentKeys){let o;r.setLogger(e.logger);{const e=t.agentGatewayUrl,r=agentKeys;o=async()=>{const o=await agentFetch(`${e}/api/v1/canvas/token`,r,{method:"POST"});if(!o.ok){const e=await o.text().catch(()=>"");throw new Error(`Canvas token returned ${o.status}: ${e}`)}return o.json()}}const n=n=>{const a=new CanvasRelayClient({canvasRelayUrl:t.canvasRelayUrl,agentKeys:agentKeys,agentId:n.id,displayName:n.displayName,avatarUrl:n.avatarUrl,tokenProvider:o,onReconnect:()=>u.onRelayReconnected(),logger:e.logger});a.connect(),r.setRelayClient(a),r.setAgentId(n.id),u.wireRelayActionHandler(),u.flushPendingCanvasLinks(),e.logger.info?.(`[beeos-claw] Canvas Relay multiplexed client connected (agentId=${n.id}, name=${n.name??"none"})`)};e.registerService({id:"beeos-canvas-relay",async start(){try{await u.waitForAgentMapping();const r=u.getDefaultPlatformAgent();if(r?.id)return void n(r);e.logger.info?.("[beeos-claw] Canvas Relay deferred: waiting for late agent sync..."),u.onceAgentMappingReady(()=>{const r=u.getDefaultPlatformAgent();if(r?.id)try{n(r)}catch(r){e.logger.warn?.(`[beeos-claw] Deferred Canvas Relay start failed: ${r}`)}else e.logger.warn?.("[beeos-claw] Agent mapping ready but no platform agent; Canvas Relay stays disabled")})}catch(r){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${r}`)}},stop(){r.getRelayClient()?.disconnect()}}),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const o={stateManager:r,logger:e.logger,writeObs:e=>n(e),isCanvasActive:e=>!!e&&u.isCanvasActiveForGatewayKey(e),getActiveSessionId:e=>u.getSessionIdForToolCall(e),getPlatformAgentId:e=>u.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>u.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>u.waitForAgentMapping()},i=new FormSchemaRegistry,s=buildCronSchemas(a,{capMs:t.cronMaxDurationCapMs});i.register(s.create),i.register(s.update);const l=new ResourceDescriptorRegistry;l.register(CRON_RESOURCE);const c=e=>async(r,o,t)=>(u.preMapToolCallSession(r),e(r,o));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:c(createRenderUiHandler(o))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:c(createCloseUiHandler(o))}),u.setCanvasState(r,{schemaRegistry:i,descriptorRegistry:l});const g=u.getFormBuilder();g&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(i),parameters:RENDER_FORM_TOOL_SCHEMA,execute:c(createRenderFormHandler({registry:i,builder:g,logger:e.logger}))});const d=u.getResourceRenderer();if(d){generateActionTools(l,e,async(e,r)=>u.gatewayRequest(e,r),d,e.logger)}const p=`\n\nTool selection guidance:\n- browser tool: Use when the user wants to OPEN, VISIT, NAVIGATE to a webpage, take SCREENSHOTS, interact with elements (click, type), or visually inspect pages.\n- beeos_search: Use ONLY for text-based information retrieval (fact-finding, research queries). Never for page interaction or screenshots.\n- beeos_fetch: Use ONLY to extract article text from a known URL. Never for visual content or screenshots.\n- ${BEEOS_FILE_RESOURCE_ROUTING_RULE}\nWhen the user says "open", "visit", "go to", "screenshot", or mentions seeing/viewing a webpage, ALWAYS prefer the browser tool.`,m=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),y=256,f=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,r)=>{if(r?.sessionKey){if(f.size>=y&&!f.has(r.sessionKey)){const e=f.keys().next().value;void 0!==e&&f.delete(e)}f.set(r.sessionKey,r.channelId)}if(isThirdPartyChannel(a,{sessionKey:r?.sessionKey,channelId:r?.channelId}))return{appendSystemContext:'This conversation is in a third-party channel (Lark/Telegram/Discord/Slack); Canvas surfaces are not visible here. Do NOT use render_ui, close_ui, or render_form. Use built-in tools directly (e.g. the `cron` tool with `action:"add"` for scheduled tasks) and respond with plain text.'};if(!r?.sessionKey)return{appendSystemContext:p};return u.isCanvasActiveForGatewayKey(r.sessionKey)?{appendSystemContext:CANVAS_SYSTEM_PROMPT+p}:{appendSystemContext:"Canvas is closed by user. Do NOT use render_ui, close_ui, or render_form. Respond with plain text only."+p}}),e.logger.debug?.("[beeos-claw] Canvas system prompt hook registered: before_prompt_build")}catch{e.logger.debug?.("[beeos-claw] before_prompt_build hook not supported")}try{e.on("before_tool_call",(e,r)=>{if(r?.sessionKey&&u.setPendingToolCallSessionKey(r.sessionKey),!m.has(e.toolName))return;const o=r?.sessionKey?f.get(r.sessionKey):void 0;return isThirdPartyChannel(a,{sessionKey:r?.sessionKey,channelId:o})?{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."}:r?.sessionKey?u.isCanvasActiveForGatewayKey(r.sessionKey)?void 0:{block:!0,blockReason:"Canvas is closed by user. Respond with plain text instead."}:{block:!0,blockReason:"Canvas session unknown. Respond with plain text instead."}}),e.logger.debug?.("[beeos-claw] Canvas tool guard registered: before_tool_call")}catch{e.logger.debug?.("[beeos-claw] before_tool_call hook not supported")}}e.logger.info?.("[beeos-claw] Canvas tools registered (render_ui, close_ui, render_form + auto-action tools)")}else e.logger.info?.("[beeos-claw] Canvas disabled by config (canvasEnabled=false)");if(t.bridge.shell.enabled&&!0===t.legacyTerminalWsEnabled){const r=createTerminalWebSocketService({config:{port:t.terminalWsPort,token:agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):"",allowedOrigins:t.terminalWsAllowedOrigins},terminalMgr:sharedTerminalManager,logger:e.logger,writeObs:n});e.registerService({id:"beeos-terminal-ws",start(){r.start(),e.logger.info?.(`[beeos-claw] Terminal WS started on port ${t.terminalWsPort}`)},stop(){r.stop(),e.logger.info?.("[beeos-claw] Terminal WS stopped")}})}else t.bridge.shell.enabled?e.logger.info?.("[beeos-claw] Legacy static-token Terminal WS disabled (default)"):e.logger.info?.("[beeos-claw] Terminal/shell disabled by config (bridge.shell.enabled=false)");if(r.openclawConfig){const o=mirrorOpenClawConfig(r.openclawConfig);o.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${o.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${o.reason}`)}}const CAPABILITY_REGISTRATION_MODES=new Set(["full","discovery","tool-discovery"]),plugin={id:"beeos-claw",name:"beeos-claw",description:"BeeOS platform bridge plugin for OpenClaw. Provides file upload/download, ACP bridge, and observability.",version:PLUGIN_VERSION,register(e){if(!_initializing)if(!e.registrationMode||CAPABILITY_REGISTRATION_MODES.has(e.registrationMode)){_initializing=!0;try{if(!hasRuntimeReplyApi(e.runtime))throw e.logger.error?.("[beeos-claw] FATAL: OpenClaw runtime.channel.reply.dispatchReplyFromConfig not exposed. Upgrade OpenClaw or revert beeos-claw."),new Error("openclaw_runtime_reply_api_unavailable");const r=initializePlugin(e);if(!r)return;const o={current:null};installUnhandledRejectionObserver(e,r.writeObs,o),registerUploadTools(e,r,o),registerArouterMediaProviders(e),registerServices(e,r,o),_pluginRegisteredAt=Date.now(),e.logger.info?.("[beeos-claw] Plugin registered successfully")}finally{_initializing=!1}}else e.logger.debug?.(`[beeos-claw] Skipping registration (mode=${e.registrationMode})`)}};export default plugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createMessageClient}from"@beeos-ai/message-sdk";import{createNodeMessageClientComposition}from"@beeos-ai/message-sdk/node";export class BeeosClawMessageClientProcessOwner{composition;configurationKey;registrations=new Set;activeStop;startInFlight;stopInFlight;configure(t){const i=JSON.stringify({identityId:t.identityId,serviceUrl:t.serviceUrl??"",scopedDeliveryKey:t.runtimeDelivery?.scopedDeliveryKey??""});if(this.composition){if(this.configurationKey!==i)throw new Error("MessageClient process owner rejected incompatible hot-reload configuration");return this.composition}return this.configurationKey=i,this.composition=composeMessageClient(t),this.composition}createRegistrationToken(){return Symbol("beeos-claw-message-client-registration")}async acquire(t,i){if(this.registrations.has(t))this.startInFlight&&await this.startInFlight;else{if(!this.composition)throw new Error("MessageClient process owner is not configured");this.registrations.add(t);try{if(this.stopInFlight&&await this.stopInFlight,this.activeStop)return;this.startInFlight||(this.startInFlight=(async()=>{const t=await i(this.composition);this.activeStop=t})().finally(()=>{this.startInFlight=void 0})),await this.startInFlight}catch(i){throw this.registrations.delete(t),i}}}async release(t){if(this.registrations.delete(t)&&(this.startInFlight&&await this.startInFlight.catch(()=>{}),!(this.registrations.size>0)&&this.activeStop)){if(!this.stopInFlight){const t=this.activeStop;this.activeStop=void 0,this.stopInFlight=t().finally(()=>{this.stopInFlight=void 0})}await this.stopInFlight}}get referenceCount(){return this.registrations.size}}export const composeMessageClient=t=>{const i=createNodeMessageClientComposition(t);return{client:createMessageClient(i),nodeTransport:i.nodeTransport}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const toMessagePart=e=>normalizeJson(e,new WeakSet);const normalizeJson=(e,t)=>{if(null===e||"string"==typeof e||"boolean"==typeof e)return e;if("number"==typeof e)return Number.isFinite(e)?e:null;if("bigint"==typeof e)return e.toString();if(Array.isArray(e)){if(t.has(e))return"[Circular]";t.add(e);const r=e.map(e=>normalizeJson(e,t));return t.delete(e),r}if("object"==typeof e){if(t.has(e))return"[Circular]";t.add(e);const r={};for(const[n,o]of Object.entries(e))void 0!==o&&"function"!=typeof o&&"symbol"!=typeof o&&(r[n]=normalizeJson(o,t));return t.delete(e),r}return String(e)};export const projectedMessages=e=>Object.values(e.getSnapshot().messages);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{deterministicReplyMessageId}from"./im-runtime-dispatch.js";export class MessageProjectionInboundConsumer{delivered=new Set;cancelledTargets=new Set;inFlightMessages=new Set;inFlightCancels=new Set;client;bindings;logger;executionJournal;maxConcurrentConversations;conversationQueue=[];queuedConversations=new Set;activeConversations=new Set;rerunConversations=new Set;laneTasks=new Set;cancelTasks=new Set;unsubscribe;activeConversationCount=0;started=!1;constructor(e){this.client=e.client,this.bindings=e.bindings,this.logger=e.logger,this.executionJournal=e.executionJournal,this.maxConcurrentConversations=Math.max(1,Math.floor(e.maxConcurrentConversations??16))}start(){this.started||(this.started=!0,this.unsubscribe=this.client.subscribe(()=>this.schedule()),this.schedule())}async stop(){this.started=!1,this.unsubscribe?.(),this.unsubscribe=void 0,this.conversationQueue.length=0,this.queuedConversations.clear(),this.rerunConversations.clear(),await Promise.allSettled([...this.laneTasks,...this.cancelTasks])}refresh(){this.schedule()}schedule(){if(!this.started)return;const e=Object.values(this.client.getSnapshot().messages).filter(isInbound).sort(compareProjectionOrder);for(const n of e)"chat_cancel"===n.type&&this.dispatchCancel(n,e);for(const n of new Set(e.filter(e=>"chat_message"===e.type).map(e=>e.conversationId)))this.enqueueConversation(n);this.pumpConversationQueue()}enqueueConversation(e){this.activeConversations.has(e)?this.rerunConversations.add(e):this.queuedConversations.has(e)||(this.queuedConversations.add(e),this.conversationQueue.push(e))}pumpConversationQueue(){for(;this.started&&this.activeConversationCount<this.maxConcurrentConversations&&this.conversationQueue.length>0;){const e=this.conversationQueue.shift();this.queuedConversations.delete(e),this.activeConversations.add(e),this.activeConversationCount+=1;const n=this.drainConversation(e).catch(n=>{this.logger.warn?.(`[beeos-claw] projected inbound lane failed conversation=${e}: ${n instanceof Error?n.message:String(n)}`)}).finally(()=>{this.laneTasks.delete(n),this.activeConversations.delete(e),this.activeConversationCount-=1,this.rerunConversations.delete(e)&&this.started&&this.enqueueConversation(e),this.pumpConversationQueue()});this.laneTasks.add(n)}}async drainConversation(e){const n=Object.values(this.client.getSnapshot().messages).filter(n=>"chat_message"===n.type&&n.conversationId===e).sort(compareProjectionOrder);for(const e of n){if(!this.started)break;const n=resolveExecutionBinding(e,this.bindings());if(!n)continue;const t=`${e.conversationId}:${e.id}`;if(this.cancelledTargets.has(t)){this.delivered.add(t);continue}if(this.delivered.has(t)||this.inFlightMessages.has(t))continue;const i=await this.reconcileRequest(e,n);if(!this.started)break;if("handled"===i){this.delivered.add(t);continue}if("pending"===i)break;if(!sameBinding(n,resolveExecutionBinding(e,this.bindings())))break;let s;try{s=await this.executionJournal.claim(t)}catch(n){this.logger.warn?.(`[beeos-claw] inbound execution claim failed conversation=${e.conversationId} message_id=${e.id}: ${n instanceof Error?n.message:String(n)}`);break}if(!this.started||"existing"===s)break;if(!sameBinding(n,resolveExecutionBinding(e,this.bindings())))break;this.inFlightMessages.add(t);try{await n.inbound.onChatMessage(e),this.delivered.add(t)}catch(n){this.logger.warn?.(`[beeos-claw] projected inbound handler failed conversation=${e.conversationId} message_id=${e.id}: ${n instanceof Error?n.message:String(n)}`);break}finally{this.inFlightMessages.delete(t)}}}dispatchCancel(e,n){const t=`${e.conversationId}:${e.id}`;if(this.delivered.has(t)||this.inFlightCancels.has(t))return;const i=resolveCancelTarget(e,n,this.bindings());if(!i)return;this.cancelledTargets.add(`${i.message.conversationId}:${i.message.id}`),this.inFlightCancels.add(t);const s=i.binding.inbound.onChatCancel(e).then(()=>{this.delivered.add(t)}).catch(n=>{this.logger.warn?.(`[beeos-claw] projected inbound cancel failed conversation=${e.conversationId} message_id=${e.id}: ${n instanceof Error?n.message:String(n)}`)}).finally(()=>{this.cancelTasks.delete(s),this.inFlightCancels.delete(t)});this.cancelTasks.add(s)}async reconcileRequest(e,n){const t=Object.values(this.client.getSnapshot().messages),i=t.filter(t=>isReplyFor(t,e,n.targetAgentId));if(i.some(isTerminalReply))return"handled";if(i.length>0)return"pending";if(t.some(t=>isReasoningFor(t,e,n.targetAgentId)))return"pending";const s=deterministicReplyMessageId("asst",e.conversationId,e.id,n.principalId);try{const t=await this.client.messages.getEnvelope(e.conversationId,s);return isReplyFor(t,e,n.targetAgentId)?isTerminalReply(t)?"handled":"pending":(this.logger.warn?.(`[beeos-claw] deterministic reply reconciliation mismatch conversation=${e.conversationId} message_id=${e.id} reply_id=${s}`),"pending")}catch(n){return 404===httpStatus(n)?"execute":(this.logger.warn?.(`[beeos-claw] deterministic reply reconciliation failed conversation=${e.conversationId} message_id=${e.id}: ${n instanceof Error?n.message:String(n)}`),"pending")}}}function isInbound(e){return"chat_message"===e.type||"chat_cancel"===e.type}function resolveExecutionBinding(e,n){if(!e.id)return;const t=asRecord(e.content),i=asRecord(t?.metadata),s=nonEmptyString(i?.delivery_principal),r=nonEmptyString(i?.target_agent_id),a=nonEmptyString(i?.runtime_epoch);return s&&r&&a?n.find(e=>e.principalId===s&&e.targetAgentId===r&&e.runtimeEpoch===a):void 0}function sameBinding(e,n){return void 0!==n&&n.principalId===e.principalId&&n.targetAgentId===e.targetAgentId&&n.runtimeEpoch===e.runtimeEpoch&&n.inbound===e.inbound}function resolveCancelTarget(e,n,t){const i=nonEmptyString(asRecord(e.content)?.target_message_id);if(!i)return;const s=n.find(n=>"chat_message"===n.type&&n.id===i&&n.conversationId===e.conversationId);if(!s)return;const r=resolveExecutionBinding(s,t),a=resolveExecutionBinding(e,t);return r&&a&&r.principalId===a.principalId&&r.targetAgentId===a.targetAgentId&&r.runtimeEpoch===a.runtimeEpoch?{message:s,binding:r}:void 0}function isReplyFor(e,n,t){return"agent_reply"===e.type&&e.conversationId===n.conversationId&&e.replyTo===n.id&&e.senderId===t}function isReasoningFor(e,n,t){return"agent_reasoning"===e.type&&e.conversationId===n.conversationId&&e.replyTo===n.id&&e.senderId===t}function isTerminalReply(e){return"agent_reply"===e.type&&("completed"===e.state||"failed"===e.state||"refused"===e.state||"cancelled"===e.state)}function httpStatus(e){return null!==e&&"object"==typeof e&&"status"in e&&"number"==typeof e.status?e.status:void 0}function asRecord(e){return null===e||"object"!=typeof e||Array.isArray(e)?void 0:e}function nonEmptyString(e){return"string"==typeof e&&e.length>0?e:void 0}function compareProjectionOrder(e,n){try{const t=BigInt(e.offset)-BigInt(n.offset);if(0n!==t)return t<0n?-1:1}catch{const t=e.offset.localeCompare(n.offset);if(0!==t)return t}return e.id.localeCompare(n.id)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const TITLE_SYSTEM_PROMPT_BASE='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/Thai (up to 24 if needed to keep all key domain terms intact).\n- Language: When a Client UI locale is provided below, write the title in THAT language even if the user wrote in a different language (translate the topic). Otherwise match the user\'s input language exactly. Mixed zh/en input → use the dominant language. Do NOT switch languages away from the required locale.\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 — never treat them as vague. Prefer the locale-native self-intro title from the Client UI locale section when present.\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"}',SELF_INTRO_BY_LOCALE={zh:"自我介绍","zh-TW":"自我介紹",ja:"自己紹介",ko:"자기소개",th:"แนะนำตัว",en:"Self Introduction"},SELF_INTRO_ALIASES=new Set(Object.values(SELF_INTRO_BY_LOCALE).map(e=>e.toLowerCase())),LOCALE_PROMPT_HINT={zh:'zh (简体中文) — write the title in Simplified Chinese. Self-intro → "自我介绍".',"zh-TW":'zh-TW (繁體中文) — write the title in Traditional Chinese. Self-intro → "自我介紹".',ja:'ja (日本語) — write the title in Japanese. Self-intro → "自己紹介". Never use Chinese "自我介绍".',ko:'ko (한국어) — write the title in Korean. Self-intro → "자기소개".',th:'th (ไทย) — write the title in Thai. Self-intro → "แนะนำตัว".',en:'en (English) — write the title in English. Self-intro → "Self Introduction".'},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"}],PLACEHOLDER_TITLES=new Set(["new chat","new conversation","新会话","新對話","新しいチャット","새 채팅","แชตใหม่"]),PLACEHOLDER_TO_LOCALE={"new chat":"en","new conversation":"en","新会话":"zh","新對話":"zh-TW","新しいチャット":"ja","새 채팅":"ko","แชตใหม่":"th"};export function inferTitleLocaleFromPlaceholder(e){if(!e)return;const t=e.trim().toLowerCase();return PLACEHOLDER_TO_LOCALE[t]??PLACEHOLDER_TO_LOCALE[e.trim()]}export function buildTitleSystemPrompt(e){return e?`${TITLE_SYSTEM_PROMPT_BASE}\n\n### Client UI locale\n${LOCALE_PROMPT_HINT[e]}\nCRITICAL: The title language MUST match this locale, even when [User Question] is in another language.`:TITLE_SYSTEM_PROMPT_BASE}export function alignTitleWithLocale(e,t){return t&&SELF_INTRO_ALIASES.has(e.trim().toLowerCase())?SELF_INTRO_BY_LOCALE[t]:e}export function isPlaceholderTitle(e){if(!e)return!0;const t=e.trim();return!t||(!!PLACEHOLDER_TITLES.has(t.toLowerCase())||(!!/^[0-9a-f]{6,12}\s*\(20\d{2}/i.test(t)||(!!/^Sender\s*\(untrusted/i.test(t)||!!/^Conversation\s+info\s*\(untrusted/i.test(t))))}export function isGuardMessage(e){const t=e.trim();return[...t].length<3||[...t].every(e=>/[\p{P}\p{S}\s]/u.test(e))}export function matchTitleTemplate(e){const t=e.trim().toLowerCase();for(const n of TITLE_TEMPLATES)if(n.match.some(e=>e.toLowerCase()===t))return/[\u4e00-\u9fff]/.test(e)?n.zh:n.en}function truncateQuestionForTitle(e){return e.length>2e3?e.slice(0,1500):e}function truncateAnswerForTitle(e){return e.length<=1e3?e:`${e.slice(0,500)}\n...\n${e.slice(-500)}`}export function fallbackTitleFromQuestion(e){const t=e.replace(/[\r\n\t]+/g," ").replace(/[。!?.!?…]+$/u,"").trim();if(!t||isGuardMessage(t)||isPlaceholderTitle(t))return;const n=[...t];return n.length<=20?t:n.slice(0,18).join("")}export async function generateTitleFromQA(e,t,n,i="title-gen"){let o,r,a=i;if(n&&("locale"in n||"logPrefix"in n||"logger"in n)){const e=n;o=e.logger,a=e.logPrefix??"title-gen",r=e.locale}else o=n;if(isGuardMessage(e))return void o?.info?.(`[${a}] SKIP guard message`);const s=matchTitleTemplate(e);if(s){const e=alignTitleWithLocale(s,r);return o?.info?.(`[${a}] template title="${e}"`),e}const l=(process.env.AROUTER_BASE_URL||"").replace(/\/$/,""),c=process.env.AROUTER_API_KEY||"";if(!l||!c)return void o?.info?.(`[${a}] SKIP: no AROUTER env vars`);const u=`[User Question]\n${truncateQuestionForTitle(e)}\n\n[Assistant Answer]\n${truncateAnswerForTitle(t)}`;o?.info?.(`[${a}] CALLING LLM q="${e.slice(0,60)}" aLen=${t.length}${r?` locale=${r}`:""}`);try{const t=await fetch(`${l}/v1/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${c}`},body:JSON.stringify({model:"openai/gpt-4o-mini",max_tokens:32,temperature:.3,messages:[{role:"system",content:buildTitleSystemPrompt(r)},{role:"user",content:u}]})});if(!t.ok){const e=await t.text().catch(()=>"");return void o?.info?.(`[${a}] FAILED status=${t.status} body=${e.slice(0,300)}`)}const n=await t.json(),i=n.choices?.[0]?.message?.content?.trim();if(!i)return;const s=i.replace(/^```json\s*/,"").replace(/```$/,"").trim();let h;try{h=JSON.parse(s).title?.trim()??""}catch{return void o?.debug?.(`[${a}] JSON parse failed: ${s}`)}if(!h||isPlaceholderTitle(h)){const t=fallbackTitleFromQuestion(e);if(t){const n=alignTitleWithLocale(t,r),i=r&&n===t&&/介绍一下你自己|介紹一下你自己|introduce yourself|自己紹介|자기소개/i.test(e)?SELF_INTRO_BY_LOCALE[r]:n;return o?.info?.(`[${a}] discarded placeholder/empty title="${h}"; fallback="${i}"`),i}return void o?.info?.(`[${a}] discarded placeholder/empty title="${h}"`)}const
|
|
1
|
+
import{randomUUID}from"node:crypto";const TITLE_SYSTEM_PROMPT_BASE='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/Thai (up to 24 if needed to keep all key domain terms intact).\n- Language: When a Client UI locale is provided below, write the title in THAT language even if the user wrote in a different language (translate the topic). Otherwise match the user\'s input language exactly. Mixed zh/en input → use the dominant language. Do NOT switch languages away from the required locale.\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 — never treat them as vague. Prefer the locale-native self-intro title from the Client UI locale section when present.\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"}',SELF_INTRO_BY_LOCALE={zh:"自我介绍","zh-TW":"自我介紹",ja:"自己紹介",ko:"자기소개",th:"แนะนำตัว",en:"Self Introduction"},SELF_INTRO_ALIASES=new Set(Object.values(SELF_INTRO_BY_LOCALE).map(e=>e.toLowerCase())),LOCALE_PROMPT_HINT={zh:'zh (简体中文) — write the title in Simplified Chinese. Self-intro → "自我介绍".',"zh-TW":'zh-TW (繁體中文) — write the title in Traditional Chinese. Self-intro → "自我介紹".',ja:'ja (日本語) — write the title in Japanese. Self-intro → "自己紹介". Never use Chinese "自我介绍".',ko:'ko (한국어) — write the title in Korean. Self-intro → "자기소개".',th:'th (ไทย) — write the title in Thai. Self-intro → "แนะนำตัว".',en:'en (English) — write the title in English. Self-intro → "Self Introduction".'},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"}],PLACEHOLDER_TITLES=new Set(["new chat","new conversation","新会话","新對話","新しいチャット","새 채팅","แชตใหม่"]),PLACEHOLDER_TO_LOCALE={"new chat":"en","new conversation":"en","新会话":"zh","新對話":"zh-TW","新しいチャット":"ja","새 채팅":"ko","แชตใหม่":"th"};export function inferTitleLocaleFromPlaceholder(e){if(!e)return;const t=e.trim().toLowerCase();return PLACEHOLDER_TO_LOCALE[t]??PLACEHOLDER_TO_LOCALE[e.trim()]}export function buildTitleSystemPrompt(e){return e?`${TITLE_SYSTEM_PROMPT_BASE}\n\n### Client UI locale\n${LOCALE_PROMPT_HINT[e]}\nCRITICAL: The title language MUST match this locale, even when [User Question] is in another language.`:TITLE_SYSTEM_PROMPT_BASE}export function alignTitleWithLocale(e,t){return t&&SELF_INTRO_ALIASES.has(e.trim().toLowerCase())?SELF_INTRO_BY_LOCALE[t]:e}export function isPlaceholderTitle(e){if(!e)return!0;const t=e.trim();return!t||(!!PLACEHOLDER_TITLES.has(t.toLowerCase())||(!!/^[0-9a-f]{6,12}\s*\(20\d{2}/i.test(t)||(!!/^Sender\s*\(untrusted/i.test(t)||!!/^Conversation\s+info\s*\(untrusted/i.test(t))))}export function isGuardMessage(e){const t=e.trim();return[...t].length<3||[...t].every(e=>/[\p{P}\p{S}\s]/u.test(e))}export function matchTitleTemplate(e){const t=e.trim().toLowerCase();for(const n of TITLE_TEMPLATES)if(n.match.some(e=>e.toLowerCase()===t))return/[\u4e00-\u9fff]/.test(e)?n.zh:n.en}function truncateQuestionForTitle(e){return e.length>2e3?e.slice(0,1500):e}function truncateAnswerForTitle(e){return e.length<=1e3?e:`${e.slice(0,500)}\n...\n${e.slice(-500)}`}export function fallbackTitleFromQuestion(e){const t=e.replace(/[\r\n\t]+/g," ").replace(/[。!?.!?…]+$/u,"").trim();if(!t||isGuardMessage(t)||isPlaceholderTitle(t))return;const n=[...t];return n.length<=20?t:n.slice(0,18).join("")}export async function generateTitleFromQA(e,t,n,i="title-gen"){let o,r,a=i;if(n&&("locale"in n||"logPrefix"in n||"logger"in n)){const e=n;o=e.logger,a=e.logPrefix??"title-gen",r=e.locale}else o=n;if(isGuardMessage(e))return void o?.info?.(`[${a}] SKIP guard message`);const s=matchTitleTemplate(e);if(s){const e=alignTitleWithLocale(s,r);return o?.info?.(`[${a}] template title="${e}"`),e}const l=(process.env.AROUTER_BASE_URL||"").replace(/\/$/,""),c=process.env.AROUTER_API_KEY||"";if(!l||!c)return void o?.info?.(`[${a}] SKIP: no AROUTER env vars`);const u=`[User Question]\n${truncateQuestionForTitle(e)}\n\n[Assistant Answer]\n${truncateAnswerForTitle(t)}`;o?.info?.(`[${a}] CALLING LLM q="${e.slice(0,60)}" aLen=${t.length}${r?` locale=${r}`:""}`);try{const t=await fetch(`${l}/v1/chat/completions`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${c}`},body:JSON.stringify({model:"openai/gpt-4o-mini",max_tokens:32,temperature:.3,messages:[{role:"system",content:buildTitleSystemPrompt(r)},{role:"user",content:u}]})});if(!t.ok){const e=await t.text().catch(()=>"");return void o?.info?.(`[${a}] FAILED status=${t.status} body=${e.slice(0,300)}`)}const n=await t.json(),i=n.choices?.[0]?.message?.content?.trim();if(!i)return;const s=i.replace(/^```json\s*/,"").replace(/```$/,"").trim();let h;try{h=JSON.parse(s).title?.trim()??""}catch{return void o?.debug?.(`[${a}] JSON parse failed: ${s}`)}if(!h||isPlaceholderTitle(h)){const t=fallbackTitleFromQuestion(e);if(t){const n=alignTitleWithLocale(t,r),i=r&&n===t&&/介绍一下你自己|介紹一下你自己|introduce yourself|自己紹介|자기소개/i.test(e)?SELF_INTRO_BY_LOCALE[r]:n;return o?.info?.(`[${a}] discarded placeholder/empty title="${h}"; fallback="${i}"`),i}return void o?.info?.(`[${a}] discarded placeholder/empty title="${h}"`)}const m=alignTitleWithLocale(h,r);return o?.info?.(m!==h?`[${a}] OK title="${h}" → locale-aligned="${m}"`:`[${a}] OK title="${h}"`),m}catch(e){return void o?.info?.(`[${a}] ERROR: ${e instanceof Error?e.message:e}`)}}export function createConversationTitleRenameIntent(e,t){return Object.freeze({conversationId:e,title:t,commandId:randomUUID()})}export async function patchMsConversationTitle(e,t){await e.conversations.update({conversationId:t.conversationId,title:t.title,idempotencyKey:t.commandId})}export async function maybeRenameImConversationTitle(e){const{client:t,conversationId:n,question:i,answer:o,logger:r}=e,a=`im-title-gen conv=${n}`;if(!n||!i.trim()||!o.trim())return void r?.debug?.(`[${a}] SKIP missing conversation/q/a`);let s;try{s=(await t.conversations.get(n)).title}catch(e){return void r?.info?.(`[${a}] GET conversation failed: ${e instanceof Error?e.message:e}`)}if(!isPlaceholderTitle(s))return void r?.debug?.(`[${a}] SKIP non-placeholder title="${s}"`);const l=inferTitleLocaleFromPlaceholder(s),c=await generateTitleFromQA(i,o,{logger:r,logPrefix:a,locale:l});if(!c)return;const u=createConversationTitleRenameIntent(n,c);try{return await patchMsConversationTitle(t,u),r?.info?.(`[${a}] PATCH MS title OK title="${c}"`),c}catch(e){return void r?.info?.(`[${a}] PATCH MS title FAILED: ${e instanceof Error?e.message:e}`)}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveRuntimeToolArguments,resolveRuntimeToolCallId,resolveRuntimeToolName}from"./tool-envelope-metadata.js";const isNonEmptyString=e=>"string"==typeof e&&e.length>0,extractBodyUpdate=(e,t)=>{if(!isNonEmptyString(t))return{kind:"noop"};if(t===e.text)return{kind:"noop"};if(t.startsWith(e.text)){const n=t.slice(e.text.length);return e.text=t,{kind:"append",text:n}}return e.text.startsWith(t)?{kind:"noop"}:(e.text=t,{kind:"replace",text:t})};export function createImReplySink(e){const t={text:""};let n,r="",o="";const s=new Map,i=new Map,a=[],l=[];let d=0,u=0;const g=(t,n,r)=>{const o=u;return e.stream.appendToolUse(t,n,r,"streaming"),u+=1,o},m=()=>(d+=1,`tool_${e.requestId}_${d}`),c=e=>{const t=a.indexOf(e);t>=0&&a.splice(t,1)},p=e=>{const t=e?a.filter(t=>s.get(t)?.name===e):a;return 1===t.length?t[0]:void 0},f=e=>{const t=l.map((e,t)=>({hint:e,index:t})).filter(({hint:t})=>!e||t.name===e);if(1!==t.length)return;const[{hint:n,index:r}]=t;return l.splice(r,1),n},h=(e,t)=>{i.set(e,t)},I=e=>i.get(e)??e,y=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),v=(t,n,r,o)=>{const i=s.get(t);if(i){const s="tool"===i.name&&"tool"!==n?n:i.name,l=!y(i.arguments)&&y(r)?r:i.arguments;return s===i.name&&l===i.arguments||(e.stream.replacePart(i.partIndex,{type:"tool_use",id:i.id,name:s,arguments:l,state:i.state}),i.name=s,i.arguments=l),o&&!a.includes(t)&&"streaming"===i.state&&a.push(t),i}const l={id:t,name:n,arguments:r,partIndex:g(t,n,r),state:"streaming",itemEventObserved:!1,toolStartObserved:!1};return s.set(t,l),o&&a.push(t),l},$=t=>{"done"!==t.state&&(e.stream.replacePart(t.partIndex,{type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done"}),t.state="done")},x=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(x).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${x(t)}`).join(",")}}`},b=(e,t,n)=>{const r=i.get(e);if(r)return v(r,t??"tool",n??{},!1);if(s.has(e))return v(e,t??"tool",n??{},!1);const o=p(t);return o?(h(e,o),v(o,t??"tool",n??{},!1)):v(e,t??"tool",n??{},!1)},_=(e,t)=>{const n=i.get(e);if(n)return v(n,t,{},!1);if(s.has(e))return v(e,t,{},!1);const r=(e=>{const t=a.filter(t=>{const n=s.get(t);return!(void 0===n||e&&n.name!==e||n.itemEventObserved)});return 1===t.length?t[0]:void 0})(t);if(r)return h(e,r),v(r,t,{},!1);const o=f(t);return v(e,t,o?.arguments??{},!1)},k={tool:0,block:0,final:0};let S=!1;const R=(t,n)=>{e.callbackQueue.enqueueSync(()=>{if((t=>e.abortSignal.aborted?(e.logger.debug?.(`[im-sink] ${t} ignored after abort requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):e.stream.isTerminated?(e.logger.debug?.(`[im-sink] ${t} ignored after stream_terminated requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):!S||(e.logger.debug?.(`[im-sink] ${t} ignored after mark_complete requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1))(t))try{n()}catch(n){const r=n instanceof Error?n.message:String(n);e.logger.warn?.(`[im-sink] ${t} mapping failed requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId} error=${r}`)}})};return{dispatcher:{sendToolResult:t=>(R("tool_progress",()=>{const n=resolveRuntimeToolCallId(t),r=resolveRuntimeToolName(t),o=n?void 0:r?p(r):p(),i=n||o?void 0:f(r);if(!n&&!o&&!i)return void e.logger.debug?.(`[im-sink] ambiguous id-less tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${r??"unknown"} pending=${a.length}`);const l=n?b(n,r,resolveRuntimeToolArguments(t)):v(o??m(),r??s.get(o??"")?.name??i.name,y(resolveRuntimeToolArguments(t))?resolveRuntimeToolArguments(t):i?.arguments??{},!1);k.tool+=1,((t,n,r)=>{const o=`${r?"1":"0"}:${x(n)}`;if(t.progressSignature===o)return;const s={type:"custom",kind:"tool_progress",data:{tool_use_id:t.id,content:n,...r?{is_error:!0}:{}}};if(void 0===t.progressPartIndex){const n=u;return e.stream.addPart(s),t.progressPartIndex=n,t.progressSignature=o,void(u+=1)}e.stream.replacePart(t.progressPartIndex,s),t.progressSignature=o})(l,(e=>{const t=[e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e));return isNonEmptyString(e.text)&&t.length>0?{text:e.text,media_urls:t}:isNonEmptyString(e.text)?e.text:1===t.length?t[0]:t.length>1?{media_urls:t}:!0===e.isError?"Tool execution failed.":""})(t),!0===t.isError),$(l),c(l.id)}),!0),sendBlockReply:n=>(R("block_reply",()=>{k.block+=1,isNonEmptyString(n.text)&&(e.stream.setBody(n.text),t.text=n.text)}),!0),sendFinalReply:n=>(R("final_reply",()=>{k.final+=1,isNonEmptyString(n.text)&&(e.stream.setBody(n.text),t.text=n.text)}),!0),waitForIdle:()=>e.callbackQueue.waitForIdle(),getQueuedCounts:()=>({...k}),getAccumulatedBody:()=>t.text,markComplete:t=>{if(1===l.length){const e=l.shift();v(m(),e.name,e.arguments,!1)}for(const n of s.values())try{"success"===t&&$(n),c(n.id)}catch(t){const r=t instanceof Error?t.message:String(t);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${n.id} error=${r}`)}l.length=0,S=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,onAgentRunStart:t=>{e.onAgentRunStart?.(t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${t} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:n=>{R("partial",()=>{const r=extractBodyUpdate(t,n.text);switch(r.kind){case"append":e.stream.appendBody(r.text);break;case"replace":e.stream.setBody(r.text)}})},onReasoningStream:t=>{R("reasoning",()=>{if(!isNonEmptyString(t.text))return;if(!n&&(n=e.openReasoningStream?.(),!n))return;const s=t.text;""===o&&""!==r&&(n.appendBody("\n"),r+="\n"),s!==o&&(s.startsWith(o)?(n.appendBody(s.slice(o.length)),o=s):o.startsWith(s)||(n.setBody(r+s),o=s))})},onReasoningEnd:()=>{R("reasoning_end",()=>{var t;""!==o&&(t=o,e.stream.appendThinking(t,"done"),u+=1,r+=o,o="")})},onAssistantMessageStart:()=>{e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)},onToolStart:t=>{R("tool_start",()=>{const n="string"==typeof t.phase?t.phase:"start",r=resolveRuntimeToolName(t)??"tool",o=resolveRuntimeToolCallId(t);if("update"===n){if(o){const e=I(o);s.has(e)&&v(e,r,resolveRuntimeToolArguments(t),!0)}return}const i=resolveRuntimeToolArguments(t);if(o){const e=b(o,r,i);return e.toolStartObserved=!0,void(a.includes(e.id)||"streaming"!==e.state||a.push(e.id))}const d=(u=r,a.map(e=>s.get(e)).filter(e=>void 0!==e&&(!u||e.name===u)&&e.itemEventObserved));var u;if(1===d.length){return void(v(d[0].id,r,i,!0).toolStartObserved=!0)}d.length>1?e.logger.debug?.(`[im-sink] ambiguous id-less tool start ignored requestId=${e.requestId} channel=${e.channelId} tool=${r} candidates=${d.length}`):l.push({name:r,arguments:i})})},onItemEvent:e=>{R("item_event",()=>{const t="string"==typeof e.kind?e.kind.toLowerCase():"";if(t&&"tool"!==t)return;const n="string"==typeof e.phase?e.phase.toLowerCase():"update",r=resolveRuntimeToolCallId(e)??("string"==typeof e.itemId&&e.itemId.trim()?e.itemId.trim():void 0),o=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);if(!r||!o)return;if("update"===n){const e=I(r),t=s.get(e);return void(t&&(v(t.id,o,{},!0).itemEventObserved=!0))}const i=_(r,o);i.itemEventObserved=!0,a.includes(i.id)||"streaming"!==i.state||a.push(i.id)})}}}}
|
|
1
|
+
import{toMessagePart}from"../message-client-types.js";import{resolveRuntimeToolArguments,resolveRuntimeToolCallId,resolveRuntimeToolName}from"./tool-envelope-metadata.js";const isNonEmptyString=e=>"string"==typeof e&&e.length>0,extractBodyUpdate=(e,t)=>{if(!isNonEmptyString(t))return{kind:"noop"};if(t===e.text)return{kind:"noop"};if(t.startsWith(e.text)){const n=t.slice(e.text.length);return e.text=t,{kind:"append",text:n}}return e.text.startsWith(t)?{kind:"noop"}:(e.text=t,{kind:"replace",text:t})};export function createImReplySink(e){const t={text:""};let n,r="",o="";const s=new Map,i=new Map,a=[],l=[];let d=0,u=0;const g=(t,n,r)=>{const o=u;return e.stream.appendToolUse(t,n,toMessagePart(r),"streaming"),u+=1,o},m=()=>(d+=1,`tool_${e.requestId}_${d}`),c=e=>{const t=a.indexOf(e);t>=0&&a.splice(t,1)},p=e=>{const t=e?a.filter(t=>s.get(t)?.name===e):a;return 1===t.length?t[0]:void 0},f=e=>{const t=l.map((e,t)=>({hint:e,index:t})).filter(({hint:t})=>!e||t.name===e);if(1!==t.length)return;const[{hint:n,index:r}]=t;return l.splice(r,1),n},h=(e,t)=>{i.set(e,t)},I=e=>i.get(e)??e,y=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),v=(t,n,r,o)=>{const i=s.get(t);if(i){const s="tool"===i.name&&"tool"!==n?n:i.name,l=!y(i.arguments)&&y(r)?r:i.arguments;return s===i.name&&l===i.arguments||(e.stream.replacePart(i.partIndex,toMessagePart({type:"tool_use",id:i.id,name:s,arguments:l,state:i.state})),i.name=s,i.arguments=l),o&&!a.includes(t)&&"streaming"===i.state&&a.push(t),i}const l={id:t,name:n,arguments:r,partIndex:g(t,n,r),state:"streaming",itemEventObserved:!1,toolStartObserved:!1};return s.set(t,l),o&&a.push(t),l},$=t=>{"done"!==t.state&&(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done"})),t.state="done")},x=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(x).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${x(t)}`).join(",")}}`},b=(e,t,n)=>{const r=i.get(e);if(r)return v(r,t??"tool",n??{},!1);if(s.has(e))return v(e,t??"tool",n??{},!1);const o=p(t);return o?(h(e,o),v(o,t??"tool",n??{},!1)):v(e,t??"tool",n??{},!1)},_=(e,t)=>{const n=i.get(e);if(n)return v(n,t,{},!1);if(s.has(e))return v(e,t,{},!1);const r=(e=>{const t=a.filter(t=>{const n=s.get(t);return!(void 0===n||e&&n.name!==e||n.itemEventObserved)});return 1===t.length?t[0]:void 0})(t);if(r)return h(e,r),v(r,t,{},!1);const o=f(t);return v(e,t,o?.arguments??{},!1)},k={tool:0,block:0,final:0};let S=!1;const R=(t,n)=>{e.callbackQueue.enqueueSync(()=>{if((t=>e.abortSignal.aborted?(e.logger.debug?.(`[im-sink] ${t} ignored after abort requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):e.stream.isTerminated?(e.logger.debug?.(`[im-sink] ${t} ignored after stream_terminated requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):!S||(e.logger.debug?.(`[im-sink] ${t} ignored after mark_complete requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1))(t))try{n()}catch(n){const r=n instanceof Error?n.message:String(n);e.logger.warn?.(`[im-sink] ${t} mapping failed requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId} error=${r}`)}})};return{dispatcher:{sendToolResult:t=>(R("tool_progress",()=>{const n=resolveRuntimeToolCallId(t),r=resolveRuntimeToolName(t),o=n?void 0:r?p(r):p(),i=n||o?void 0:f(r);if(!n&&!o&&!i)return void e.logger.debug?.(`[im-sink] ambiguous id-less tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${r??"unknown"} pending=${a.length}`);const l=n?b(n,r,resolveRuntimeToolArguments(t)):v(o??m(),r??s.get(o??"")?.name??i.name,y(resolveRuntimeToolArguments(t))?resolveRuntimeToolArguments(t):i?.arguments??{},!1);k.tool+=1,((t,n,r)=>{const o=`${r?"1":"0"}:${x(n)}`;if(t.progressSignature===o)return;const s=toMessagePart({type:"custom",kind:"tool_progress",data:{tool_use_id:t.id,content:n,...r?{is_error:!0}:{}}});if(void 0===t.progressPartIndex){const n=u;return e.stream.addPart(s),t.progressPartIndex=n,t.progressSignature=o,void(u+=1)}e.stream.replacePart(t.progressPartIndex,s),t.progressSignature=o})(l,(e=>{const t=[e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e));return isNonEmptyString(e.text)&&t.length>0?{text:e.text,media_urls:t}:isNonEmptyString(e.text)?e.text:1===t.length?t[0]:t.length>1?{media_urls:t}:!0===e.isError?"Tool execution failed.":""})(t),!0===t.isError),$(l),c(l.id)}),!0),sendBlockReply:n=>(R("block_reply",()=>{k.block+=1,isNonEmptyString(n.text)&&(e.stream.setBody(n.text),t.text=n.text)}),!0),sendFinalReply:n=>(R("final_reply",()=>{k.final+=1,isNonEmptyString(n.text)&&(e.stream.setBody(n.text),t.text=n.text)}),!0),waitForIdle:()=>e.callbackQueue.waitForIdle(),getQueuedCounts:()=>({...k}),getAccumulatedBody:()=>t.text,markComplete:t=>{if(1===l.length){const e=l.shift();v(m(),e.name,e.arguments,!1)}for(const n of s.values())try{"success"===t&&$(n),c(n.id)}catch(t){const r=t instanceof Error?t.message:String(t);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${n.id} error=${r}`)}l.length=0,S=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,onAgentRunStart:t=>{e.onAgentRunStart?.(t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${t} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:n=>{R("partial",()=>{const r=extractBodyUpdate(t,n.text);switch(r.kind){case"append":e.stream.appendBody(r.text);break;case"replace":e.stream.setBody(r.text)}})},onReasoningStream:t=>{R("reasoning",()=>{if(!isNonEmptyString(t.text))return;if(!n&&(n=e.openReasoningStream?.(),!n))return;const s=t.text;""===o&&""!==r&&(n.appendBody("\n"),r+="\n"),s!==o&&(s.startsWith(o)?(n.appendBody(s.slice(o.length)),o=s):o.startsWith(s)||(n.setBody(r+s),o=s))})},onReasoningEnd:()=>{R("reasoning_end",()=>{var t;""!==o&&(t=o,e.stream.appendThinking(t,"done"),u+=1,r+=o,o="")})},onAssistantMessageStart:()=>{e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)},onToolStart:t=>{R("tool_start",()=>{const n="string"==typeof t.phase?t.phase:"start",r=resolveRuntimeToolName(t)??"tool",o=resolveRuntimeToolCallId(t);if("update"===n){if(o){const e=I(o);s.has(e)&&v(e,r,resolveRuntimeToolArguments(t),!0)}return}const i=resolveRuntimeToolArguments(t);if(o){const e=b(o,r,i);return e.toolStartObserved=!0,void(a.includes(e.id)||"streaming"!==e.state||a.push(e.id))}const d=(u=r,a.map(e=>s.get(e)).filter(e=>void 0!==e&&(!u||e.name===u)&&e.itemEventObserved));var u;if(1===d.length){return void(v(d[0].id,r,i,!0).toolStartObserved=!0)}d.length>1?e.logger.debug?.(`[im-sink] ambiguous id-less tool start ignored requestId=${e.requestId} channel=${e.channelId} tool=${r} candidates=${d.length}`):l.push({name:r,arguments:i})})},onItemEvent:e=>{R("item_event",()=>{const t="string"==typeof e.kind?e.kind.toLowerCase():"";if(t&&"tool"!==t)return;const n="string"==typeof e.phase?e.phase.toLowerCase():"update",r=resolveRuntimeToolCallId(e)??("string"==typeof e.itemId&&e.itemId.trim()?e.itemId.trim():void 0),o=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);if(!r||!o)return;if("update"===n){const e=I(r),t=s.get(e);return void(t&&(v(t.id,o,{},!0).itemEventObserved=!0))}const i=_(r,o);i.itemEventObserved=!0,a.includes(i.id)||"streaming"!==i.state||a.push(i.id)})}}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RUNTIME_HASH_DOMAINS}from"@beeos-ai/beeos-types/runtime";import{runtimeMessageHash,semanticEqual,validateRuntimeCancelEnvelope,validateRuntimeRequestEnvelope}from"./runtime-message-channel-contract.js";export class RuntimeMessageChannelProtocolError extends Error{status;code;constructor(e,t,o){super(e),this.status=t,this.code=o,this.name="RuntimeMessageChannelProtocolError"}}export class MessageServiceRuntimeMessageChannel{options;constructor(e){if(this.options=e,!e.scopedDeliveryKey)throw new Error("Message runtime delivery credential is required")}async read(e){const t=await this.post("/api/v1/runtime/deliveries/read",e.lease,{schemaVersion:1,maxCount:e.maxCount,blockMs:e.blockMs},void 0,e.signal);if(!record(t)||!exactKeys(t,["status","deliveries"])||"deliveries"!==t.status||!Array.isArray(t.deliveries)||t.deliveries.length>e.maxCount||!t.deliveries.every(isDelivery)||new Set(t.deliveries.map(e=>e.deliveryId)).size!==t.deliveries.length)throw new RuntimeMessageChannelProtocolError("Message Service returned invalid runtime deliveries");return t.deliveries}async renew(e){const t=await this.post("/api/v1/runtime/deliveries/renew",e.lease,{schemaVersion:1,deliveryIds:e.deliveryIds});if(!(record(t)&&exactKeys(t,["status","renewed","notPending"])&&"renewed"===t.status&&strings(t.renewed)&&strings(t.notPending)&&exactIdPartition(e.deliveryIds,t.renewed,t.notPending)))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid delivery renewal");return{renewed:t.renewed,notPending:t.notPending}}async acknowledge(e){const t=await this.post("/api/v1/runtime/deliveries/ack",e.lease,{schemaVersion:1,deliveryIds:e.deliveryIds});if(!(record(t)&&exactKeys(t,["status","deliveryIds"])&&"acknowledged"===t.status&&strings(t.deliveryIds)&&sameIds(t.deliveryIds,e.deliveryIds)))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid delivery acknowledgement")}async history(e){const t=`/api/v1/runtime/operations/${encodeURIComponent(e.operationId)}/history`,o=await this.fetch(t,e.lease,{method:"GET"});if(404===o.status)return{status:"not_found"};if(410===o.status)return{status:"expired"};const n=await responseJson(o);if(!o.ok)throw protocolFailure(o,n,"runtime history read failed");if(!isHistory(n,e.operationId))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid operation history");return{status:"found",snapshot:n}}async append(e){const t=`/api/v1/runtime/operations/${encodeURIComponent(e.operationId)}/messages`,o=appendResult(await this.post(t,e.lease,{schemaVersion:1,type:e.type,payload:e.payload},e.executionGrant)),n="runtime_rpc_response"===e.type?e.payload.requestMessageId:void 0;if(o.message.channel_id!==e.operationId||o.message.type!==e.type||o.message.in_reply_to!==n||!semanticEqual(o.message.payload,e.payload))throw new RuntimeMessageChannelProtocolError("Message Service append response correlation mismatch");return o}async post(e,t,o,n,r){const i=await this.fetch(e,t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(o),...r?{signal:r}:{}},n),s=await responseJson(i);if(!i.ok)throw protocolFailure(i,s,`runtime message-channel POST ${e} failed`);return s}async fetch(e,t,o,n){return await this.options.transport.fetchRuntimeChannel(new URL(e,this.options.serviceUrl),{runtimeLeaseCredential:t.runtimeLeaseCredential,scopedDeliveryKey:this.options.scopedDeliveryKey,...n?{executionGrant:n}:{}},o,{retryTransientStatus:!1})}}function appendResult(e){if(!record(e)||!exactKeys(e,["status","disposition","message"])||"persisted"!==e.status||"created"!==e.disposition&&"reused"!==e.disposition||!envelope(e.message))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid append result");return{disposition:e.disposition,message:e.message}}function protocolFailure(e,t,o){const n=record(t)&&"string"==typeof t.code?t.code:void 0,r=record(t)&&"string"==typeof t.message?t.message:o;return new RuntimeMessageChannelProtocolError(`${r} (${e.status})`,e.status,n)}async function responseJson(e){try{return await e.json()}catch{return}}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function strings(e){return Array.isArray(e)&&e.every(e=>"string"==typeof e)}function sameIds(e,t){return e.length===t.length&&[...e].sort().every((e,o)=>e===[...t].sort()[o])}function exactIdPartition(e,...t){return new Set(e).size===e.length&&!t.some(e=>new Set(e).size!==e.length)&&sameIds(t.flat(),e)}function envelope(e){if(!record(e))return!1;return exactKeys(e,["message_id","offset","channel_id","type","payload","publisher_id","idempotency_key","created_at","state",...void 0===e.in_reply_to?[]:["in_reply_to"]])&&"string"==typeof e.message_id&&Number.isInteger(e.offset)&&e.offset>=1&&"string"==typeof e.channel_id&&["runtime_rpc_request","runtime_operation_started","runtime_operation_progress","runtime_effect_committed","runtime_projection_pending","runtime_projection_synced","runtime_projection_blocked","runtime_rpc_response","runtime_operation_cancel_requested"].includes(e.type)&&record(e.payload)&&"string"==typeof e.publisher_id&&"string"==typeof e.idempotency_key&&"string"==typeof e.created_at&&"completed"===e.state}function isDelivery(e){return record(e)&&exactKeys(e,["deliveryId","redelivered","idleMs","message",...void 0===e.executionGrant?[]:["executionGrant"]])&&"string"==typeof e.deliveryId&&"boolean"==typeof e.redelivered&&Number.isInteger(e.idleMs)&&e.idleMs>=0&&envelope(e.message)&&(void 0===e.executionGrant||"string"==typeof e.executionGrant&&/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(e.executionGrant))&&["runtime_rpc_request","runtime_operation_cancel_requested"].includes(e.message.type)}function isHistory(e,t){if(!record(e))return!1;const o=["schemaVersion","operationId","requestMessageId","requestHash","status","effectState","terminal","terminalSource","cancelRequested","latestOffset","projectionState","request","messages",...["started","latestProgress","effectCommitted","projectionPending","projectionSynced","projectionBlocked","cancel","final"].filter(t=>void 0!==e[t])];if(!(exactKeys(e,o)&&1===e.schemaVersion&&"string"==typeof e.operationId&&e.operationId===t&&"string"==typeof e.requestMessageId&&"string"==typeof e.requestHash&&["queued","running","runtime_committed","projection_pending","projection_blocked","succeeded","failed","cancelled","expired","outcome_unknown"].includes(e.status)&&["","committed"].includes(e.effectState)&&"boolean"==typeof e.terminal&&["none","final_message","deadline"].includes(e.terminalSource)&&"boolean"==typeof e.cancelRequested&&["","reserved","pending","synced","blocked","not_applicable"].includes(e.projectionState)&&Array.isArray(e.messages)&&e.messages.every(envelope)&&Number.isInteger(e.latestOffset)&&!(e.latestOffset<1)))return!1;const n=validateRuntimeRequestEnvelope(e.request);if(!n.ok||e.request.channel_id!==e.operationId||n.envelope.payload.operationId!==e.operationId||n.envelope.payload.requestMessageId!==e.requestMessageId||n.envelope.payload.requestHash!==e.requestHash)return!1;const r=n.envelope.payload;if(void 0!==e.started&&!correlatedEnvelope(e.started,"runtime_operation_started",r,["schemaVersion","operationId","requestMessageId","requestHash","executionGrantHash","handlerIdentity","dispatchEpoch","method","executionMode","claimedAt"]))return!1;if(void 0!==e.latestProgress&&!correlatedEnvelope(e.latestProgress,"runtime_operation_progress",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","phase","progress"]))return!1;if(!(void 0===e.effectCommitted||correlatedEnvelope(e.effectCommitted,"runtime_effect_committed",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","effectRecord","effectRecordHash","committedAt"])&&validEffectCommitted(e.effectCommitted.payload,r)))return!1;if(void 0!==e.projectionPending&&!correlatedEnvelope(e.projectionPending,"runtime_projection_pending",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision"]))return!1;if(void 0!==e.projectionSynced&&!correlatedEnvelope(e.projectionSynced,"runtime_projection_synced",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision",...void 0===e.projectionSynced.payload.platformAgentId?[]:["platformAgentId"]]))return!1;if(void 0!==e.projectionBlocked&&!correlatedEnvelope(e.projectionBlocked,"runtime_projection_blocked",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision","errorCode",...void 0===e.projectionBlocked.payload.causeCode?[]:["causeCode"]]))return!1;if(void 0!==e.cancel){const t=validateRuntimeCancelEnvelope(e.cancel);if(!t.ok||!sameCorrelation(t.envelope,r))return!1}if(void 0!==e.final&&!correlatedEnvelope(e.final,"runtime_rpc_response",r,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","outcome","response","completedAt",...void 0===e.final.payload.serviceResultHash?[]:["serviceResultHash"],...void 0===e.final.payload.effectRecordHash?[]:["effectRecordHash"]]))return!1;const i=e.messages;if(0===i.length||e.latestOffset!==i.length||i.some((t,o)=>t.offset!==o+1||t.channel_id!==e.operationId)||!semanticEqual(i[0],e.request)||!historyMessagesMatchSummaries(i,e)||!projectionStateMatches(e))return!1;if(e.terminal!==("none"!==e.terminalSource)||"committed"===e.effectState!=(void 0!==e.effectCommitted)||void 0!==e.effectCommitted&&void 0===e.started||"queued"===e.status&&(void 0!==e.started||void 0!==e.effectCommitted)||["running","runtime_committed","projection_pending","projection_blocked"].includes(e.status)&&void 0===e.started)return!1;if("none"===e.terminalSource)return void 0===e.final&&["queued","running","runtime_committed","projection_pending","projection_blocked"].includes(e.status);if("deadline"===e.terminalSource)return void 0===e.final&&(void 0===e.started?"expired"===e.status:"outcome_unknown"===e.status);if(!envelope(e.final))return!1;const s=e.final.payload.outcome,a="succeeded"===s?"succeeded":"cancelled"===s?"cancelled":"expired"===s?"expired":"outcome_unknown"===s?"outcome_unknown":"projection_blocked"===s?"projection_blocked":"failed"===s?"failed":void 0;return e.status===a}function validEffectCommitted(e,t){const o=e.effectRecord;return!!(record(o)&&exactKeys(o,["schemaVersion","operationId","method","coreResultRef","coreResultHash",...void 0===o.projectionRevision?[]:["projectionRevision"]])&&1===o.schemaVersion&&o.operationId===t.operationId&&o.method===t.method&&"string"==typeof o.coreResultRef&&0!==o.coreResultRef.length&&"string"==typeof o.coreResultHash&&/^[0-9a-f]{64}$/.test(o.coreResultHash)&&(void 0===o.projectionRevision||"string"==typeof o.projectionRevision&&/^(0|[1-9][0-9]*)$/.test(o.projectionRevision))&&"string"==typeof e.effectRecordHash&&/^[0-9a-f]{64}$/.test(e.effectRecordHash)&&runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,o)===e.effectRecordHash&&"string"==typeof e.committedAt&&Number.isFinite(Date.parse(e.committedAt)))}function projectionStateMatches(e){const t=e.effectCommitted?.payload?.effectRecord?.projectionRevision,o="deadline"===e.terminalSource,n=e=>record(e)&&record(e.payload)&&e.payload.revision===t;switch(e.projectionState){case"":return void 0===e.effectCommitted&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||["queued","running","succeeded","failed","cancelled","expired","outcome_unknown"].includes(e.status));case"not_applicable":return void 0!==e.effectCommitted&&void 0===t&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||["runtime_committed","succeeded","outcome_unknown"].includes(e.status));case"reserved":return void 0!==e.effectCommitted&&void 0!==t&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||"runtime_committed"===e.status);case"pending":return void 0!==t&&n(e.projectionPending)&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||"projection_pending"===e.status);case"synced":return void 0!==t&&n(e.projectionPending)&&n(e.projectionSynced)&&void 0===e.projectionBlocked&&(o||["runtime_committed","succeeded"].includes(e.status));case"blocked":return void 0!==t&&n(e.projectionPending)&&void 0===e.projectionSynced&&n(e.projectionBlocked)&&(o||"projection_blocked"===e.status);default:return!1}}function historyMessagesMatchSummaries(e,t){const o=t=>e.filter(e=>e.type===t),n=(e,t,n=1)=>{const r=o(e);return r.length<=n&&(void 0===t?0===r.length:r.length>0&&semanticEqual(t,r.at(-1)))};if(!(n("runtime_rpc_request",t.request)&&n("runtime_operation_started",t.started)&&n("runtime_effect_committed",t.effectCommitted)&&n("runtime_projection_pending",t.projectionPending)&&n("runtime_projection_synced",t.projectionSynced)&&n("runtime_projection_blocked",t.projectionBlocked)&&n("runtime_operation_cancel_requested",t.cancel)&&n("runtime_rpc_response",t.final)))return!1;const r=o("runtime_operation_progress");if(void 0===t.latestProgress?0!==r.length:0===r.length||!semanticEqual(t.latestProgress,r.at(-1)))return!1;if(t.cancelRequested!==(void 0!==t.cancel))return!1;const i=e.findIndex(e=>"runtime_rpc_response"===e.type);return-1===i||i===e.length-1}function correlatedEnvelope(e,t,o,n){const r="runtime_rpc_response"===t?o.requestMessageId:void 0;return!(!envelope(e)||e.type!==t||!exactKeys(e.payload,n)||e.in_reply_to!==r)&&sameCorrelation(e,o)}function sameCorrelation(e,t){const o=e.payload;return e.channel_id===t.operationId&&o.operationId===t.operationId&&o.requestMessageId===t.requestMessageId&&o.requestHash===t.requestHash&&o.dispatchEpoch===t.dispatchEpoch&&o.method===t.method}function exactKeys(e,t){const o=Object.keys(e).sort(),n=[...t].sort();return o.length===n.length&&o.every((e,t)=>e===n[t])}
|
|
1
|
+
import{RUNTIME_HASH_DOMAINS}from"@beeos-ai/beeos-types/runtime";import{runtimeMessageHash,semanticEqual,validateRuntimeCancelEnvelope,validateRuntimeRequestEnvelope}from"./runtime-message-channel-contract.js";export class RuntimeMessageChannelProtocolError extends Error{status;code;constructor(e,t,o){super(e),this.status=t,this.code=o,this.name="RuntimeMessageChannelProtocolError"}}export class MessageServiceRuntimeMessageChannel{options;constructor(e){if(this.options=e,!e.scopedDeliveryKey)throw new Error("Message runtime delivery credential is required")}async read(e){const t=await this.post("/api/v1/runtime/deliveries/read",e.lease,{schemaVersion:1,maxCount:e.maxCount,blockMs:e.blockMs},void 0,e.signal);if(!record(t)||!exactKeys(t,["status","deliveries"])||"deliveries"!==t.status||!Array.isArray(t.deliveries)||t.deliveries.length>e.maxCount||!t.deliveries.every(isDelivery)||new Set(t.deliveries.map(e=>e.deliveryId)).size!==t.deliveries.length)throw new RuntimeMessageChannelProtocolError("Message Service returned invalid runtime deliveries");return t.deliveries}async renew(e){const t=await this.post("/api/v1/runtime/deliveries/renew",e.lease,{schemaVersion:1,deliveryIds:e.deliveryIds});if(!(record(t)&&exactKeys(t,["status","renewed","notPending"])&&"renewed"===t.status&&strings(t.renewed)&&strings(t.notPending)&&exactIdPartition(e.deliveryIds,t.renewed,t.notPending)))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid delivery renewal");return{renewed:t.renewed,notPending:t.notPending}}async acknowledge(e){const t=await this.post("/api/v1/runtime/deliveries/ack",e.lease,{schemaVersion:1,deliveryIds:e.deliveryIds});if(!(record(t)&&exactKeys(t,["status","deliveryIds"])&&"acknowledged"===t.status&&strings(t.deliveryIds)&&sameIds(t.deliveryIds,e.deliveryIds)))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid delivery acknowledgement")}async history(e){const t=`/api/v1/runtime/operations/${encodeURIComponent(e.operationId)}/history`,o=await this.fetch(t,e.lease,{method:"GET"});if(404===o.status)return{status:"not_found"};if(410===o.status)return{status:"expired"};const r=await responseJson(o);if(!o.ok)throw protocolFailure(o,r,"runtime history read failed");if(!isHistory(r,e.operationId))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid operation history");return{status:"found",snapshot:r}}async append(e){const t=`/api/v1/runtime/operations/${encodeURIComponent(e.operationId)}/messages`,o=appendResult(await this.post(t,e.lease,{schemaVersion:1,type:e.type,payload:e.payload},e.executionGrant)),r="runtime_rpc_response"===e.type?e.payload.requestMessageId:void 0;if(o.message.channel_id!==e.operationId||o.message.type!==e.type||o.message.in_reply_to!==r||!semanticEqual(o.message.payload,e.payload))throw new RuntimeMessageChannelProtocolError("Message Service append response correlation mismatch");return o}async post(e,t,o,r,n){const i=await this.fetch(e,t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(o),...n?{signal:n}:{}},r),s=await responseJson(i);if(!i.ok)throw protocolFailure(i,s,`runtime message-channel POST ${e} failed`);return s}async fetch(e,t,o,r){return await this.options.transport.fetchRuntimeChannel(new URL(e,this.options.serviceUrl),{runtimeLeaseCredential:t.runtimeLeaseCredential,scopedDeliveryKey:this.options.scopedDeliveryKey,...r?{executionGrant:r}:{}},o,{retryTransientStatus:!1})}}export function decodeRuntimeBrokerDelivery(e){if(!isDelivery(e))throw new RuntimeMessageChannelProtocolError("Message Service returned an invalid runtime delivery");return e}export function decodeRuntimeOperationHistory(e,t){if(!isHistory(e,t))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid operation history");return e}export function decodeRuntimeAppendMessage(e,t){if(!envelope(e))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid append result");const o="runtime_rpc_response"===t.type?t.payload.requestMessageId:void 0;if(e.channel_id!==t.operationId||e.type!==t.type||e.in_reply_to!==o||!semanticEqual(e.payload,t.payload))throw new RuntimeMessageChannelProtocolError("Message Service append response correlation mismatch");return e}function appendResult(e){if(!record(e)||!exactKeys(e,["status","disposition","message"])||"persisted"!==e.status||"created"!==e.disposition&&"reused"!==e.disposition||!envelope(e.message))throw new RuntimeMessageChannelProtocolError("Message Service returned invalid append result");return{disposition:e.disposition,message:e.message}}function protocolFailure(e,t,o){const r=record(t)&&"string"==typeof t.code?t.code:void 0,n=record(t)&&"string"==typeof t.message?t.message:o;return new RuntimeMessageChannelProtocolError(`${n} (${e.status})`,e.status,r)}async function responseJson(e){try{return await e.json()}catch{return}}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function strings(e){return Array.isArray(e)&&e.every(e=>"string"==typeof e)}function sameIds(e,t){return e.length===t.length&&[...e].sort().every((e,o)=>e===[...t].sort()[o])}function exactIdPartition(e,...t){return new Set(e).size===e.length&&!t.some(e=>new Set(e).size!==e.length)&&sameIds(t.flat(),e)}function envelope(e){if(!record(e))return!1;return exactKeys(e,["message_id","offset","channel_id","type","payload","publisher_id","idempotency_key","created_at","state",...void 0===e.in_reply_to?[]:["in_reply_to"]])&&"string"==typeof e.message_id&&Number.isInteger(e.offset)&&e.offset>=1&&"string"==typeof e.channel_id&&["runtime_rpc_request","runtime_operation_started","runtime_operation_progress","runtime_effect_committed","runtime_projection_pending","runtime_projection_synced","runtime_projection_blocked","runtime_rpc_response","runtime_operation_cancel_requested"].includes(e.type)&&record(e.payload)&&"string"==typeof e.publisher_id&&"string"==typeof e.idempotency_key&&"string"==typeof e.created_at&&"completed"===e.state}function isDelivery(e){return record(e)&&exactKeys(e,["deliveryId","redelivered","idleMs","message",...void 0===e.executionGrant?[]:["executionGrant"]])&&"string"==typeof e.deliveryId&&"boolean"==typeof e.redelivered&&Number.isInteger(e.idleMs)&&e.idleMs>=0&&envelope(e.message)&&(void 0===e.executionGrant||"string"==typeof e.executionGrant&&/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(e.executionGrant))&&["runtime_rpc_request","runtime_operation_cancel_requested"].includes(e.message.type)}function isHistory(e,t){if(!record(e))return!1;const o=["schemaVersion","operationId","requestMessageId","requestHash","status","effectState","terminal","terminalSource","cancelRequested","latestOffset","projectionState","request","messages",...["started","latestProgress","effectCommitted","projectionPending","projectionSynced","projectionBlocked","cancel","final"].filter(t=>void 0!==e[t])];if(!(exactKeys(e,o)&&1===e.schemaVersion&&"string"==typeof e.operationId&&e.operationId===t&&"string"==typeof e.requestMessageId&&"string"==typeof e.requestHash&&["queued","running","runtime_committed","projection_pending","projection_blocked","succeeded","failed","cancelled","expired","outcome_unknown"].includes(e.status)&&["","committed"].includes(e.effectState)&&"boolean"==typeof e.terminal&&["none","final_message","deadline"].includes(e.terminalSource)&&"boolean"==typeof e.cancelRequested&&["","reserved","pending","synced","blocked","not_applicable"].includes(e.projectionState)&&Array.isArray(e.messages)&&e.messages.every(envelope)&&Number.isInteger(e.latestOffset)&&!(e.latestOffset<1)))return!1;const r=validateRuntimeRequestEnvelope(e.request);if(!r.ok||e.request.channel_id!==e.operationId||r.envelope.payload.operationId!==e.operationId||r.envelope.payload.requestMessageId!==e.requestMessageId||r.envelope.payload.requestHash!==e.requestHash)return!1;const n=r.envelope.payload;if(void 0!==e.started&&!correlatedEnvelope(e.started,"runtime_operation_started",n,["schemaVersion","operationId","requestMessageId","requestHash","executionGrantHash","handlerIdentity","dispatchEpoch","method","executionMode","claimedAt"]))return!1;if(void 0!==e.latestProgress&&!correlatedEnvelope(e.latestProgress,"runtime_operation_progress",n,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","phase","progress"]))return!1;if(!(void 0===e.effectCommitted||correlatedEnvelope(e.effectCommitted,"runtime_effect_committed",n,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","effectRecord","effectRecordHash","committedAt"])&&validEffectCommitted(e.effectCommitted.payload,n)))return!1;if(void 0!==e.projectionPending&&!correlatedEnvelope(e.projectionPending,"runtime_projection_pending",n,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision"]))return!1;if(void 0!==e.projectionSynced&&!correlatedEnvelope(e.projectionSynced,"runtime_projection_synced",n,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision",...void 0===e.projectionSynced.payload.platformAgentId?[]:["platformAgentId"]]))return!1;if(void 0!==e.projectionBlocked&&!correlatedEnvelope(e.projectionBlocked,"runtime_projection_blocked",n,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","revision","errorCode",...void 0===e.projectionBlocked.payload.causeCode?[]:["causeCode"]]))return!1;if(void 0!==e.cancel){const t=validateRuntimeCancelEnvelope(e.cancel);if(!t.ok||!sameCorrelation(t.envelope,n))return!1}if(void 0!==e.final&&!correlatedEnvelope(e.final,"runtime_rpc_response",n,["schemaVersion","operationId","requestMessageId","requestHash","dispatchEpoch","method","outcome","response","completedAt",...void 0===e.final.payload.serviceResultHash?[]:["serviceResultHash"],...void 0===e.final.payload.effectRecordHash?[]:["effectRecordHash"]]))return!1;const i=e.messages;if(0===i.length||e.latestOffset!==i.length||i.some((t,o)=>t.offset!==o+1||t.channel_id!==e.operationId)||!semanticEqual(i[0],e.request)||!historyMessagesMatchSummaries(i,e)||!projectionStateMatches(e))return!1;if(e.terminal!==("none"!==e.terminalSource)||"committed"===e.effectState!=(void 0!==e.effectCommitted)||void 0!==e.effectCommitted&&void 0===e.started||"queued"===e.status&&(void 0!==e.started||void 0!==e.effectCommitted)||["running","runtime_committed","projection_pending","projection_blocked"].includes(e.status)&&void 0===e.started)return!1;if("none"===e.terminalSource)return void 0===e.final&&["queued","running","runtime_committed","projection_pending","projection_blocked"].includes(e.status);if("deadline"===e.terminalSource)return void 0===e.final&&(void 0===e.started?"expired"===e.status:"outcome_unknown"===e.status);if(!envelope(e.final))return!1;const s=e.final.payload.outcome,a="succeeded"===s?"succeeded":"cancelled"===s?"cancelled":"expired"===s?"expired":"outcome_unknown"===s?"outcome_unknown":"projection_blocked"===s?"projection_blocked":"failed"===s?"failed":void 0;return e.status===a}function validEffectCommitted(e,t){const o=e.effectRecord;return!!(record(o)&&exactKeys(o,["schemaVersion","operationId","method","coreResultRef","coreResultHash",...void 0===o.projectionRevision?[]:["projectionRevision"]])&&1===o.schemaVersion&&o.operationId===t.operationId&&o.method===t.method&&"string"==typeof o.coreResultRef&&0!==o.coreResultRef.length&&"string"==typeof o.coreResultHash&&/^[0-9a-f]{64}$/.test(o.coreResultHash)&&(void 0===o.projectionRevision||"string"==typeof o.projectionRevision&&/^(0|[1-9][0-9]*)$/.test(o.projectionRevision))&&"string"==typeof e.effectRecordHash&&/^[0-9a-f]{64}$/.test(e.effectRecordHash)&&runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,o)===e.effectRecordHash&&"string"==typeof e.committedAt&&Number.isFinite(Date.parse(e.committedAt)))}function projectionStateMatches(e){const t=e.effectCommitted?.payload?.effectRecord?.projectionRevision,o="deadline"===e.terminalSource,r=e=>record(e)&&record(e.payload)&&e.payload.revision===t;switch(e.projectionState){case"":return void 0===e.effectCommitted&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||["queued","running","succeeded","failed","cancelled","expired","outcome_unknown"].includes(e.status));case"not_applicable":return void 0!==e.effectCommitted&&void 0===t&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||["runtime_committed","succeeded","outcome_unknown"].includes(e.status));case"reserved":return void 0!==e.effectCommitted&&void 0!==t&&void 0===e.projectionPending&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||"runtime_committed"===e.status);case"pending":return void 0!==t&&r(e.projectionPending)&&void 0===e.projectionSynced&&void 0===e.projectionBlocked&&(o||"projection_pending"===e.status);case"synced":return void 0!==t&&r(e.projectionPending)&&r(e.projectionSynced)&&void 0===e.projectionBlocked&&(o||["runtime_committed","succeeded"].includes(e.status));case"blocked":return void 0!==t&&r(e.projectionPending)&&void 0===e.projectionSynced&&r(e.projectionBlocked)&&(o||"projection_blocked"===e.status);default:return!1}}function historyMessagesMatchSummaries(e,t){const o=t=>e.filter(e=>e.type===t),r=(e,t,r=1)=>{const n=o(e);return n.length<=r&&(void 0===t?0===n.length:n.length>0&&semanticEqual(t,n.at(-1)))};if(!(r("runtime_rpc_request",t.request)&&r("runtime_operation_started",t.started)&&r("runtime_effect_committed",t.effectCommitted)&&r("runtime_projection_pending",t.projectionPending)&&r("runtime_projection_synced",t.projectionSynced)&&r("runtime_projection_blocked",t.projectionBlocked)&&r("runtime_operation_cancel_requested",t.cancel)&&r("runtime_rpc_response",t.final)))return!1;const n=o("runtime_operation_progress");if(void 0===t.latestProgress?0!==n.length:0===n.length||!semanticEqual(t.latestProgress,n.at(-1)))return!1;if(t.cancelRequested!==(void 0!==t.cancel))return!1;const i=e.findIndex(e=>"runtime_rpc_response"===e.type);return-1===i||i===e.length-1}function correlatedEnvelope(e,t,o,r){const n="runtime_rpc_response"===t?o.requestMessageId:void 0;return!(!envelope(e)||e.type!==t||!exactKeys(e.payload,r)||e.in_reply_to!==n)&&sameCorrelation(e,o)}function sameCorrelation(e,t){const o=e.payload;return e.channel_id===t.operationId&&o.operationId===t.operationId&&o.requestMessageId===t.requestMessageId&&o.requestHash===t.requestHash&&o.dispatchEpoch===t.dispatchEpoch&&o.method===t.method}function exactKeys(e,t){const o=Object.keys(e).sort(),r=[...t].sort();return o.length===r.length&&o.every((e,t)=>e===r[t])}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RUNTIME_ERROR_DESCRIPTORS,RUNTIME_HASH_DOMAINS,RUNTIME_INVOCATION_PARITY_ENTRIES,RUNTIME_INVOCATION_RECOVERY_CLASSES,SERVICE_CANONICAL_COMMAND_NAMES,mapCanonicalResultToService}from"@beeos-ai/beeos-types/runtime";import{reduceRuntimeRecoveryPolicy}from"@beeos-ai/beeos-types/runtime/internal";import{verifyAndCreateMessageServiceCoreExecutionAttempt}from"./core-execution-attempt-context.js";import{awaitRuntimeProjection,hasEffectReceiptHandoff,RuntimeCommandError}from"./runtime-command-dispatcher.js";import{createRuntimeAppendTimingRecorder}from"./runtime-append-timing.js";import{RUNTIME_MESSAGE_TYPES,runtimeMessageBase,runtimeMessageHash,semanticEqual,validateRuntimeCancelEnvelope,validateRuntimeRequestEnvelope}from"./runtime-message-channel-contract.js";import{RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{RuntimeTransportFailurePolicy}from"./runtime-transport-failure-policy.js";export class MsRuntimeMessageChannelAdapter{dispatcher;channel;verifier;ports;journal;getLease;options;controller;running;workers=new Map;poisonJournal;renewTransportFailurePolicy=new RuntimeTransportFailurePolicy;receiptHistory=this;constructor(e,t,o,n,a,r,i){if(this.dispatcher=e,this.channel=t,this.verifier=o,this.ports=n,this.journal=a,this.getLease=r,this.options=i,!i.poisonJournal)throw new Error("durable poison journal is required");if(this.poisonJournal=i.poisonJournal,!Number.isSafeInteger(i.poisonDeliveryLimit??3)||(i.poisonDeliveryLimit??3)<=0)throw new Error("poison delivery limit must be a positive integer")}start(){if(this.running)return;const e=new AbortController;this.controller=e;const t=this.loop(e.signal).catch(t=>{e.signal.aborted||this.observeDeliveryError(t)}).finally(()=>{this.running===t&&(this.running=void 0)});this.running=t}async stop(e){this.controller?.abort();for(const e of this.workers.values())e.cancel?.();this.running&&await abortable(this.running,e)}async readOperationHistory(e){const t=this.getLease();if(!t)return{status:"unavailable"};try{return await this.channel.history({operationId:e.operationId,lease:t})}catch{return{status:"unavailable"}}}async loop(e){let t;(()=>{t||(t=setInterval(()=>{this.renewActive().catch(e=>this.observeDeliveryError(e))},this.options.renewIntervalMs??2e4),t.unref?.())})();try{for(;!e.aborted;){const t=this.getLease();if(!t){await delay(this.options.idleDelayMs??500,e);continue}let o;try{o=await this.channel.read({maxCount:this.options.readCount??32,blockMs:this.options.blockMs??5e3,lease:t,signal:e})}catch(t){if(e.aborted)return;this.observeDeliveryError(t),await delay(this.options.idleDelayMs??500,e);continue}for(const n of o){if(e.aborted)break;if(n.message.type===RUNTIME_MESSAGE_TYPES.cancel){await this.handleCancel(n,t).catch(e=>this.observeDeliveryError(e));continue}const o=n.message.channel_id;if(this.workers.has(o))continue;const a={deliveryId:n.deliveryId,claimedLease:t,completion:Promise.resolve()};a.completion=this.handleRequest(n,t,e,e=>{a.cancel=e}).catch(e=>this.observeDeliveryError(e)).finally(()=>{this.workers.delete(o)}),this.workers.set(o,a)}}}finally{t&&clearInterval(t),await Promise.allSettled([...this.workers.values()].map(e=>e.completion))}}observeDeliveryError(e){try{this.options.onDeliveryError?.(e)}catch{}}observeRequestFailure(e,t,o){try{this.options.onRequestFailure?.({operationId:e,method:t,error:o})}catch{}}async renewActive(){const e=this.getLease();if(!e)return;const t=[];for(const o of this.workers.values())sameLeaseBinding(o.claimedLease,e)?t.push(o.deliveryId):o.cancel?.();if(!t.length)return;let o;try{o=await this.channel.renew({deliveryIds:t,lease:e})}catch(e){if(this.observeDeliveryError(e),this.renewTransportFailurePolicy.recordFailure()){const e=new Set(t);for(const t of this.workers.values())e.has(t.deliveryId)&&t.cancel?.()}return}if(this.renewTransportFailurePolicy.recordSuccess(),o.notPending.length){const e=new Set(o.notPending);for(const t of this.workers.values())e.has(t.deliveryId)&&t.cancel?.()}}async handleCancel(e,t){const o=validateRuntimeCancelEnvelope(e.message);if(!o.ok)return void await this.handlePoison(e,`invalid_cancel:${o.reason}`,t);const n=o.envelope.payload,a=await this.channel.history({operationId:n.operationId,lease:t});if("found"===a.status&&a.snapshot.terminal&&"final_message"===a.snapshot.terminalSource)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});if("not_found"===a.status||"expired"===a.status)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});const r=a.snapshot.request.payload;r.operationId===n.operationId&&r.requestMessageId===n.requestMessageId&&r.requestHash===n.requestHash&&r.dispatchEpoch===n.dispatchEpoch&&r.method===n.method?(await this.journal.observeCancellation({operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,eventId:n.eventId}),this.workers.get(n.operationId)?.cancel?.(),await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t})):await this.handlePoison(e,"cancel_history_mismatch",t)}async handleRequest(e,t,o,n){const a=validateRuntimeRequestEnvelope(e.message);if(!a.ok)return void await this.handlePoison(e,`invalid_request:${a.reason}`,t);const r=a.envelope.payload,i=e.executionGrant,s=createRuntimeAppendTimingRecorder("runtime-op",{operationId:r.operationId,method:r.method}),c=new AbortController;n(()=>c.abort());const d=async()=>{await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:this.currentLease(t)}),s.mark("ack")},l=await this.channel.history({operationId:r.operationId,lease:t});if(s.mark("history"),"not_found"===l.status||"expired"===l.status)return void await d();if(l.snapshot.terminal&&"final_message"===l.snapshot.terminalSource)return void await d();if(this.options.allowedMethods&&!this.options.allowedMethods.has(r.method))return await this.reject(r,"unsupported_method",t),void await d();if("deadline"===l.snapshot.terminalSource||Date.parse(r.executionDeadlineAt)<=Date.now()||!l.snapshot.started&&Date.parse(r.claimDeadlineAt)<=Date.now())return await this.reject(r,"execution_deadline_exceeded",t),void await d();const u=await this.journal.getCancellation(r.operationId);s.mark("journal-cancel-read");const p=l.snapshot.cancelRequested||null!==u;if(p&&"queued"===l.snapshot.status)return await this.reject(r,"cancelled_before_dispatch",t),void await d();if(r.instanceId!==t.instanceId||r.runtimeHandlerIdentity!==t.handlerIdentity||r.dispatchEpoch!==t.runtimeEpoch)return await this.reject(r,"authority_mismatch",t),void await d();const m=l.snapshot.request.payload;if(l.snapshot.operationId!==r.operationId||l.snapshot.requestMessageId!==r.requestMessageId||l.snapshot.requestHash!==r.requestHash||!semanticEqual(m,r))return void await this.handlePoison(e,"request_history_mismatch",t);const h=l.snapshot.started?.payload,I=void 0!==h&&h.operationId===r.operationId&&h.requestMessageId===r.requestMessageId&&h.requestHash===r.requestHash&&h.executionGrantHash===r.executionGrantHash&&h.handlerIdentity===t.handlerIdentity&&h.dispatchEpoch===t.runtimeEpoch&&h.method===r.method,E=await this.journal.get(r.operationId);s.mark("journal-prior");const f=await this.journal.acceptMessageRequest({operationId:r.operationId,command:r.command.name,paramsHash:r.paramsHash,runtimeEpoch:r.dispatchEpoch,requestMessageId:r.requestMessageId,requestHash:r.requestHash});s.mark("journal-accept");const v=I?E:f,R=decideMessageServiceRecovery(l.snapshot,r,v,I);if("already_terminal"===R)return void await d();if(!CORE_RECOVERY_DECISIONS.has(R))return await this.markOutcomeUnknownIfMutable(f),await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN",`Types recovery policy requires ${R}; this runtime cannot safely replay Core`),(new Date).toISOString())}),void await d();if(await this.verifier.refresh(),s.mark("verifier-refresh"),!i)return I?await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN","retained started proof has no live execution grant"),(new Date).toISOString())}):await this.reject(r,"invalid_execution_grant",t),void await d();this.verifier.verifyAndDecodeGrant(i)||await this.verifier.refresh(!0);const _=()=>c.abort();let y;o.addEventListener("abort",_,{once:!0});try{y=await verifyAndCreateMessageServiceCoreExecutionAttempt({request:r,executionGrant:i,runtimeEpoch:t.runtimeEpoch,lease:t,verifier:this.verifier,allowExpiredGrant:I,abortSignal:c.signal,resolvePorts:async()=>await this.ports.resolve({request:r,executionGrant:i,command:r.command,getLease:()=>this.currentLease(t),...this.journalLineage(),journalRecord:f,abortSignal:c.signal})})}catch(e){if(o.removeEventListener("abort",_),c.abort(),!(e instanceof RuntimeResourceResolutionError))throw e;if("transient"===e.kind)return;return I?(await this.markOutcomeUnknownIfMutable(f),await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN","eager resources cannot be reconstructed after retained started proof"),(new Date).toISOString())})):await this.reject(r,"integrity_mismatch"===e.kind?"resource_integrity_mismatch":"resource_resolution_rejected",t),void await d()}if(!y.ok)return o.removeEventListener("abort",_),c.abort(),I?(await this.markOutcomeUnknownIfMutable(f),await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(r,new RuntimeCommandError("OUTCOME_UNKNOWN","execution grant cannot be revalidated after retained started proof"),(new Date).toISOString())})):await this.reject(r,"invalid_execution_grant",t),void await d();if(!p||"prepared"!==f.phase&&"started"!==f.phase||c.abort(),c.signal.aborted&&!I)return o.removeEventListener("abort",_),await this.reject(r,"cancelled_before_dispatch",t),void await d();s.mark("attempt"),I?this.currentLease(t):(await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),executionGrant:i,type:RUNTIME_MESSAGE_TYPES.started,payload:{...runtimeMessageBase(r),executionMode:"start",executionGrantHash:r.executionGrantHash,handlerIdentity:t.handlerIdentity,claimedAt:f.messageBinding?.acceptedAt??f.updatedAt}}),s.mark("append-started"));const g=this.dispatcher.dispatch(r.command,y.context);g.completion.catch(()=>{}),n(()=>{c.abort(),g.cancel()}),c.signal.aborted&&g.cancel();const M=this.forwardProgress(r,g.progress,t).catch(()=>{});let w,S=!1;try{if(("mcp/set"===r.method||"mcp/unset"===r.method)&&hasEffectReceiptHandoff(g)){const e=await g.effectCommitted;if(e){if(e.operationId!==r.operationId||e.method!==r.command.name)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core MCP receipt correlation mismatch");const o=runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:r.method,result:e.result}),n={schemaVersion:1,operationId:r.operationId,method:r.method,coreResultRef:`sha256:${o}`,coreResultHash:o};w=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,n),S=!0,await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(r),effectRecord:n,effectRecordHash:w,committedAt:e.committedAt}}),await g.acknowledgeEffectReceipt({operationId:r.operationId,method:r.command.name,effectRecordHash:w})}}const o=await g.completion;await M,s.mark("core");const n=await this.journal.get(r.operationId);if(s.mark("journal-after-core"),!n||!isCommittedCorePhase(n.phase))throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core completed without a committed local journal record");const a=n.updatedAt;let i,c;if("query"!==RUNTIME_INVOCATION_RECOVERY_CLASSES[r.method]){const e=isAgentProjectionMethod(r.method),c=g.getSnapshot().projection,l=n.agentProjectionIntent??n.agentProjectionPreparationFailure;if(e&&!l)throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent effect committed without an atomic projection preparation record");if(!w){const e=l?.coreResultHash??runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:r.method,result:o}),i=n.agentProjectionIntent?.projectionRevision??(methodRequiresProjectionRevision(r.method)?c?.revision??`${Math.max(0,n.revision)}`:void 0),d={schemaVersion:1,operationId:r.operationId,method:r.method,coreResultRef:`sha256:${e}`,coreResultHash:e,...i?{projectionRevision:i}:{}};w=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,d),S=!0,await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(r),effectRecord:d,effectRecordHash:w,committedAt:a}}),s.mark("append-effect")}let u=e?await awaitRuntimeProjection(g):c;if("session/set_model"===r.method&&"synced"!==u?.state&&(u=await this.projectSessionModelAfterEffect(r,y.context,n,u),s.mark("set-model-projection")),e&&n.agentProjectionIntent&&(!u||u.revision!==n.agentProjectionIntent.projectionRevision))throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent projection receipt revision mismatch");if("pending"===u?.state)return void await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionPending,payload:{...runtimeMessageBase(r),revision:u.revision}});if("synced"===u?.state&&await this.publishProjectionSynced(r,t,{...u,state:"synced"}),"blocked"===u?.state){await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionBlocked,payload:{...runtimeMessageBase(r),revision:u.revision,errorCode:"PROJECTION_BLOCKED"}});const e=RUNTIME_ERROR_DESCRIPTORS.PROJECTION_BLOCKED;return await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:{...runtimeMessageBase(r),outcome:"projection_blocked",response:{jsonrpc:"2.0",id:r.request.id,error:{code:e.jsonRpcCode,message:"PROJECTION_BLOCKED",data:{code:"PROJECTION_BLOCKED",operationId:r.operationId,effectState:"committed",retryMode:e.retryMode}}},effectRecordHash:w,completedAt:a}}),void await d()}i=e?u?.platformAgentId:void 0}try{c=sanitizeRuntimeServiceResult(r.method,o,y.context,i)}catch(e){throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed Core result cannot be mapped safely",void 0,{cause:e})}const l=runtimeMessageHash(RUNTIME_HASH_DOMAINS.serviceResult,{method:r.method,result:c});S=!0;const u=await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:{...runtimeMessageBase(r),outcome:"succeeded",response:{jsonrpc:"2.0",id:r.request.id,result:c},serviceResultHash:l,...w?{effectRecordHash:w}:{},completedAt:a}});if(s.mark("append-final"),this.observeRequestTerminalAccepted(e.deliveryId,u.message),("mcp/set"===r.method||"mcp/unset"===r.method)&&hasEffectReceiptHandoff(g)&&"function"==typeof g.acknowledgeTerminalReceipt){if(!w)throw new RuntimeCommandError("OUTCOME_UNKNOWN","MCP terminal response lacks its committed effect hash");await g.acknowledgeTerminalReceipt({operationId:r.operationId,method:r.command.name,effectRecordHash:w,terminalMessageId:u.message.message_id})}await d(),this.observeRequestAcknowledged(e.deliveryId,u.message)}catch(e){const o=await this.journal.get(r.operationId);if(o&&isCommittedCorePhase(o.phase)&&S)throw e;const n=!o||!isCommittedCorePhase(o.phase)||e instanceof RuntimeCommandError&&"OUTCOME_UNKNOWN"===e.code?e:new RuntimeCommandError("OUTCOME_UNKNOWN","Core effect committed before terminal mapping",void 0,{cause:e});this.observeRequestFailure(r.operationId,r.method,n);const a=failurePayload(r,n,o?.updatedAt??(new Date).toISOString());await this.channel.append({operationId:r.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:a}),s.mark("append-terminal-failure"),await d()}finally{s.finish(),c.abort(),o.removeEventListener("abort",_)}}async handlePoison(e,t,o){this.currentLease(o);(await this.poisonJournal.record({delivery:e,reason:durablePoisonReason(t),limit:this.options.poisonDeliveryLimit??3})).terminal&&await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:this.currentLease(o)})}observeRequestTerminalAccepted(e,t){try{this.options.onRequestTerminalAccepted?.({deliveryId:e,message:t})}catch{}}observeRequestAcknowledged(e,t){try{this.options.onRequestAcknowledged?.({deliveryId:e,message:t})}catch{}}async forwardProgress(e,t,o){for await(const n of t)await this.channel.append({operationId:e.operationId,lease:this.currentLease(o),type:RUNTIME_MESSAGE_TYPES.progress,payload:{...runtimeMessageBase(e),phase:n.phase,progress:n}})}async reject(e,t,o){await this.channel.append({operationId:e.operationId,lease:this.currentLease(o),type:RUNTIME_MESSAGE_TYPES.final,payload:prestartFailurePayload(e,t)})}async markOutcomeUnknownIfMutable(e){"prepared"!==e.phase&&"started"!==e.phase||await this.journal.transition({operationId:e.operationId,expectedRevision:e.revision,phase:"outcome_unknown",errorCode:"OUTCOME_UNKNOWN"})}async projectSessionModelAfterEffect(e,t,o,n){const a=Object.freeze({state:"pending",revision:n?.revision??`${Math.max(0,o.revision)}`}),r=e.command;if("session.setModel"!==r.name||"conversation"!==r.context.scope)return a;const i=t.sessionModelProjection;if(!i)return a;try{await i.project({conversationId:r.context.conversation.runtimeConversationUid,modelOverrideId:r.params.modelOverrideId});const e=Object.freeze({state:"synced",revision:a.revision});try{const t=await this.journal.get(o.operationId)??o;if("projection_committed"===t.phase&&"synced"===t.projectionReceipt?.snapshot.state)return Object.freeze({state:"synced",revision:t.projectionReceipt.snapshot.revision});await this.journal.transition({operationId:t.operationId,expectedRevision:t.revision,phase:"projection_committed",projectionSnapshot:e})}catch{}return e}catch(e){return this.observeDeliveryError(e),a}}async publishProjectionSynced(e,t,o){const n=this.currentLease(t),a=await this.channel.history({operationId:e.operationId,lease:n});if("found"===a.status&&"synced"===a.snapshot.projectionState)return;const r={...runtimeMessageBase(e),revision:o.revision};("found"!==a.status||"reserved"===a.snapshot.projectionState||"pending"!==a.snapshot.projectionState&&void 0===a.snapshot.projectionPending)&&await this.channel.append({operationId:e.operationId,lease:n,type:RUNTIME_MESSAGE_TYPES.projectionPending,payload:r}),await this.channel.append({operationId:e.operationId,lease:n,type:RUNTIME_MESSAGE_TYPES.projectionSynced,payload:{...r,...o.platformAgentId?{platformAgentId:o.platformAgentId}:{}}})}journalLineage(){return this.options.getJournalLineage?.()??{journalStoreId:this.options.journalStoreId,journalGeneration:this.options.journalGeneration}}currentLease(e){const t=this.getLease();if(!t||t.instanceId!==e.instanceId||t.leaseId!==e.leaseId||t.runtimeEpoch!==e.runtimeEpoch||t.handlerIdentity!==e.handlerIdentity||!Number.isFinite(Date.parse(t.leaseExpiresAt))||Date.parse(t.leaseExpiresAt)<=Date.now())throw new RuntimeCommandError("INSTANCE_EPOCH_MISMATCH","runtime message lease changed");return t}}function durablePoisonReason(e){const t=e.replace(/[^a-z0-9_:.-]+/gi,"_").replace(/^_+|_+$/g,"").slice(0,256);if(!t)throw new Error("poison reason is empty");return t}function isCommittedCorePhase(e){return"effect_committed"===e||"projection_committed"===e}const CORE_RECOVERY_DECISIONS=new Set(["execute","resume","replay_effect_committed","resume_projection","publish_committed_result","publish_projected_result","publish_terminal_result"]);function decideMessageServiceRecovery(e,t,o,n){const a=RUNTIME_INVOCATION_PARITY_ENTRIES.find(e=>e.acpMethod===t.method),r=e.terminal?"terminal":"blocked"===e.projectionState?"projection_blocked":"synced"===e.projectionState?"projection_synced":"pending"===e.projectionState?"projection_pending":e.effectCommitted?"runtime_committed":n?"running":"queued",i="failed"===o?.phase||"outcome_unknown"===o?.phase?"terminal":o?.phase,s=o&&o.runtimeEpoch===t.dispatchEpoch&&(void 0===o.messageBinding||o.messageBinding.requestMessageId===t.requestMessageId&&o.messageBinding.requestHash===t.requestHash)?"continuous":"discontinuous";return reduceRuntimeRecoveryPolicy({recoveryClass:a?.recoveryClass??RUNTIME_INVOCATION_RECOVERY_CLASSES[t.method],projectionRequired:"mcp/set"===t.method||"mcp/unset"===t.method||(a?.projectionRequired??!1),messageServicePhase:r,executionBoundaryCrossed:n,lineageContinuity:s,journalState:o?"available":"unavailable",...i?{journalPhase:i}:{}})}export function sanitizeRuntimeServiceResult(e,t,o,n){if("session/clear"===e)return serviceClearResult(t,o?.historyBoundary);if("session/set_model"===e)return serviceSetModelResult(t,o);if(!(e in SERVICE_CANONICAL_COMMAND_NAMES))return t;const a="cron/status"===e&&record(t)?{enabled:t.enabled,jobCount:t.jobCount,..."number"==typeof t.nextWakeAtMs?{nextWakeAtMs:t.nextWakeAtMs}:{}}:t;return mapCanonicalResultToService(e,a,{...n?{platformAgentId:n}:{},classifySkillOrigin:e=>e.bundled?{origin:"bundled",slug:e.slug}:{origin:"legacy_runtime",slug:e.slug},resolveMcpView:e=>({name:e.name,transport:e.transport,origin:"legacy_runtime",...void 0===e.connected?{}:{connected:e.connected},...void 0===e.connectionTimeoutMs?{}:{connectionTimeoutMs:e.connectionTimeoutMs}}),sanitizeDiagnostic:e=>/^[A-Z][A-Z0-9_]{0,63}$/.test(e)?e:"RUNTIME_EXECUTION_FAILED"})}function serviceClearResult(e,t){if(!record(e)||!0!==e.cleared||"string"!=typeof e.historyGeneration||!t||"message-service"!==t.kind)throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed clear result lacks its Message Service receipt");const o=t;if(!o.committedIntentId)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Message Service clear receipt is unavailable");return{...e,clearIntentId:o.committedIntentId()}}function serviceSetModelResult(e,t){if(!record(e)||"string"!=typeof e.effectiveModelId||!t)throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed set_model result is invalid");const o=t.verifiedMessageGrant?.claims.command;if(!o||"session.setModel"!==o.name||"conversation"!==o.context.scope)throw new RuntimeCommandError("OUTCOME_UNKNOWN","set_model Service target receipt is unavailable");const n=o.params.modelOverrideId;return{conversationId:o.context.conversation.runtimeConversationUid,...null===n?{defaultModelId:e.effectiveModelId}:{},modelOverrideId:n,effectiveModelId:e.effectiveModelId,effectState:"committed",projectionState:"synced",operationId:t.operationId}}function isAgentProjectionMethod(e){return"agent/create"===e||"agent/update"===e||"agent/delete"===e||"agent/applyTemplate"===e}function methodRequiresProjectionRevision(e){return isAgentProjectionMethod(e)||"session/set_model"===e}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function sameLeaseBinding(e,t){return e.instanceId===t.instanceId&&e.leaseId===t.leaseId&&e.handlerIdentity===t.handlerIdentity&&e.runtimeEpoch===t.runtimeEpoch}function failurePayload(e,t,o){const n=t instanceof RuntimeCommandError?t.code:"RUNTIME_EXECUTION_FAILED",a=n in RUNTIME_ERROR_DESCRIPTORS?n:"RUNTIME_EXECUTION_FAILED",r="OUTCOME_UNKNOWN"===a||"INSTANCE_EPOCH_MISMATCH"===a?"outcome_unknown":"COMMAND_EXPIRED"===a?"expired":"OPERATION_CANCELLED"===a?"cancelled":"failed",i="outcome_unknown"===r?"OUTCOME_UNKNOWN":a,s=RUNTIME_ERROR_DESCRIPTORS[i],c="outcome_unknown"===r?"outcome_unknown":r;return{...runtimeMessageBase(e),outcome:r,response:{jsonrpc:"2.0",id:e.request.id,error:{code:s.jsonRpcCode,message:i,data:{code:i,operationId:e.operationId,effectState:c,retryMode:s.retryMode}}},completedAt:o}}function prestartFailurePayload(e,t){const o="invalid_execution_grant"===t?"INVALID_EXECUTION_GRANT":"unsupported_method"===t?"METHOD_NOT_SUPPORTED":"authority_mismatch"===t?"INSTANCE_EPOCH_MISMATCH":"execution_deadline_exceeded"===t?"COMMAND_EXPIRED":"cancelled_before_dispatch"===t?"OPERATION_CANCELLED":"resource_resolution_rejected"===t?"INVALID_INVOCATION":"RUNTIME_EXECUTION_FAILED",n=RUNTIME_ERROR_DESCRIPTORS[o in RUNTIME_ERROR_DESCRIPTORS?o:"RUNTIME_EXECUTION_FAILED"],a="COMMAND_EXPIRED"===o?"expired":"OPERATION_CANCELLED"===o?"cancelled":"failed";return{...runtimeMessageBase(e),outcome:a,response:{jsonrpc:"2.0",id:e.request.id,error:{code:n.jsonRpcCode,message:o,data:{code:o,operationId:e.operationId,effectState:a,retryMode:n.retryMode}}},completedAt:(new Date).toISOString()}}function abortable(e,t){return t?t.aborted?Promise.resolve():new Promise((o,n)=>{const a=()=>o();t.addEventListener("abort",a,{once:!0}),e.then(()=>{t.removeEventListener("abort",a),o()},e=>{t.removeEventListener("abort",a),n(e)})}):e}function delay(e,t){return new Promise(o=>{if(t?.aborted)return o();const n=()=>{clearTimeout(a),o()},a=setTimeout(()=>{t?.removeEventListener("abort",n),o()},e);t?.addEventListener("abort",n,{once:!0})})}
|
|
1
|
+
import{RUNTIME_ERROR_DESCRIPTORS,RUNTIME_HASH_DOMAINS,RUNTIME_INVOCATION_PARITY_ENTRIES,RUNTIME_INVOCATION_RECOVERY_CLASSES,SERVICE_CANONICAL_COMMAND_NAMES,mapCanonicalResultToService}from"@beeos-ai/beeos-types/runtime";import{reduceRuntimeRecoveryPolicy}from"@beeos-ai/beeos-types/runtime/internal";import{verifyAndCreateMessageServiceCoreExecutionAttempt}from"./core-execution-attempt-context.js";import{awaitRuntimeProjection,hasEffectReceiptHandoff,RuntimeCommandError}from"./runtime-command-dispatcher.js";import{createRuntimeAppendTimingRecorder}from"./runtime-append-timing.js";import{RUNTIME_MESSAGE_TYPES,runtimeMessageBase,runtimeMessageHash,semanticEqual,validateRuntimeCancelEnvelope,validateRuntimeRequestEnvelope}from"./runtime-message-channel-contract.js";import{RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{RuntimeTransportFailurePolicy}from"./runtime-transport-failure-policy.js";export class MsRuntimeMessageChannelAdapter{dispatcher;channel;verifier;ports;journal;getLease;options;controller;running;externalDeliveryOwner;workers=new Map;poisonJournal;renewTransportFailurePolicy=new RuntimeTransportFailurePolicy;receiptHistory=this;constructor(e,t,o,n,r,a,i){if(this.dispatcher=e,this.channel=t,this.verifier=o,this.ports=n,this.journal=r,this.getLease=a,this.options=i,!i.poisonJournal)throw new Error("durable poison journal is required");if(this.poisonJournal=i.poisonJournal,!Number.isSafeInteger(i.poisonDeliveryLimit??3)||(i.poisonDeliveryLimit??3)<=0)throw new Error("poison delivery limit must be a positive integer")}start(){if(this.externalDeliveryOwner)return void this.externalDeliveryOwner.start();if(this.running)return;const e=new AbortController;this.controller=e;const t=this.loop(e.signal).catch(t=>{e.signal.aborted||this.observeDeliveryError(t)}).finally(()=>{this.running===t&&(this.running=void 0)});this.running=t}async stop(e){this.controller?.abort();for(const e of this.workers.values())e.cancel?.();if(this.externalDeliveryOwner)return await this.externalDeliveryOwner.stop(e),void await Promise.allSettled([...this.workers.values()].map(e=>e.completion));this.running&&await abortable(this.running,e)}attachExternalDeliveryOwner(e){if(this.running||this.externalDeliveryOwner)throw new Error("runtime delivery owner is already attached");this.externalDeliveryOwner=e}async acceptDelivery(e,t,o){if(this.currentLease(t),e.message.type===RUNTIME_MESSAGE_TYPES.cancel)return void await this.handleCancel(e,t);const n=validateRuntimeRequestEnvelope(e.message),r=n.ok?n.envelope.payload.operationId:e.message.channel_id;if(this.workers.has(r))return;const a={deliveryId:e.deliveryId,claimedLease:t,completion:Promise.resolve()};a.completion=this.handleRequest(e,t,o,e=>{a.cancel=e}).catch(e=>this.observeDeliveryError(e)).finally(()=>{this.workers.delete(r)}),this.workers.set(r,a),await a.completion}async readOperationHistory(e){const t=this.getLease();if(!t)return{status:"unavailable"};try{return await this.channel.history({operationId:e.operationId,lease:t})}catch{return{status:"unavailable"}}}async loop(e){let t;(()=>{t||(t=setInterval(()=>{this.renewActive().catch(e=>this.observeDeliveryError(e))},this.options.renewIntervalMs??2e4),t.unref?.())})();try{for(;!e.aborted;){const t=this.getLease();if(!t){await delay(this.options.idleDelayMs??500,e);continue}let o;try{o=await this.channel.read({maxCount:this.options.readCount??32,blockMs:this.options.blockMs??5e3,lease:t,signal:e})}catch(t){if(e.aborted)return;this.observeDeliveryError(t),await delay(this.options.idleDelayMs??500,e);continue}for(const n of o){if(e.aborted)break;if(n.message.type===RUNTIME_MESSAGE_TYPES.cancel){await this.handleCancel(n,t).catch(e=>this.observeDeliveryError(e));continue}const o=n.message.channel_id;if(this.workers.has(o))continue;const r={deliveryId:n.deliveryId,claimedLease:t,completion:Promise.resolve()};r.completion=this.handleRequest(n,t,e,e=>{r.cancel=e}).catch(e=>this.observeDeliveryError(e)).finally(()=>{this.workers.delete(o)}),this.workers.set(o,r)}}}finally{t&&clearInterval(t),await Promise.allSettled([...this.workers.values()].map(e=>e.completion))}}observeDeliveryError(e){try{this.options.onDeliveryError?.(e)}catch{}}observeRequestFailure(e,t,o){try{this.options.onRequestFailure?.({operationId:e,method:t,error:o})}catch{}}async renewActive(){const e=this.getLease();if(!e)return;const t=[];for(const o of this.workers.values())sameLeaseBinding(o.claimedLease,e)?t.push(o.deliveryId):o.cancel?.();if(!t.length)return;let o;try{o=await this.channel.renew({deliveryIds:t,lease:e})}catch(e){if(this.observeDeliveryError(e),this.renewTransportFailurePolicy.recordFailure()){const e=new Set(t);for(const t of this.workers.values())e.has(t.deliveryId)&&t.cancel?.()}return}if(this.renewTransportFailurePolicy.recordSuccess(),o.notPending.length){const e=new Set(o.notPending);for(const t of this.workers.values())e.has(t.deliveryId)&&t.cancel?.()}}async handleCancel(e,t){const o=validateRuntimeCancelEnvelope(e.message);if(!o.ok)return void await this.handlePoison(e,`invalid_cancel:${o.reason}`,t);const n=o.envelope.payload,r=await this.channel.history({operationId:n.operationId,lease:t});if("found"===r.status&&r.snapshot.terminal&&"final_message"===r.snapshot.terminalSource)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});if("not_found"===r.status||"expired"===r.status)return void await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t});const a=r.snapshot.request.payload;a.operationId===n.operationId&&a.requestMessageId===n.requestMessageId&&a.requestHash===n.requestHash&&a.dispatchEpoch===n.dispatchEpoch&&a.method===n.method?(await this.journal.observeCancellation({operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,eventId:n.eventId}),this.workers.get(n.operationId)?.cancel?.(),await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:t})):await this.handlePoison(e,"cancel_history_mismatch",t)}async handleRequest(e,t,o,n){const r=validateRuntimeRequestEnvelope(e.message);if(!r.ok)return void await this.handlePoison(e,`invalid_request:${r.reason}`,t);const a=r.envelope.payload,i=e.executionGrant,s=createRuntimeAppendTimingRecorder("runtime-op",{operationId:a.operationId,method:a.method}),c=new AbortController;n(()=>c.abort());const d=async()=>{await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:this.currentLease(t)}),s.mark("ack")},l=await this.channel.history({operationId:a.operationId,lease:t});if(s.mark("history"),"not_found"===l.status||"expired"===l.status)return void await d();if(l.snapshot.terminal&&"final_message"===l.snapshot.terminalSource)return void await d();if(this.options.allowedMethods&&!this.options.allowedMethods.has(a.method))return await this.reject(a,"unsupported_method",t),void await d();if("deadline"===l.snapshot.terminalSource||Date.parse(a.executionDeadlineAt)<=Date.now()||!l.snapshot.started&&Date.parse(a.claimDeadlineAt)<=Date.now())return await this.reject(a,"execution_deadline_exceeded",t),void await d();const u=await this.journal.getCancellation(a.operationId);s.mark("journal-cancel-read");const p=l.snapshot.cancelRequested||null!==u;if(p&&"queued"===l.snapshot.status)return await this.reject(a,"cancelled_before_dispatch",t),void await d();if(a.instanceId!==t.instanceId||a.runtimeHandlerIdentity!==t.handlerIdentity||a.dispatchEpoch!==t.runtimeEpoch)return await this.reject(a,"authority_mismatch",t),void await d();const m=l.snapshot.request.payload;if(l.snapshot.operationId!==a.operationId||l.snapshot.requestMessageId!==a.requestMessageId||l.snapshot.requestHash!==a.requestHash||!semanticEqual(m,a))return void await this.handlePoison(e,"request_history_mismatch",t);const h=l.snapshot.started?.payload,I=void 0!==h&&h.operationId===a.operationId&&h.requestMessageId===a.requestMessageId&&h.requestHash===a.requestHash&&h.executionGrantHash===a.executionGrantHash&&h.handlerIdentity===t.handlerIdentity&&h.dispatchEpoch===t.runtimeEpoch&&h.method===a.method,E=await this.journal.get(a.operationId);s.mark("journal-prior");const f=await this.journal.acceptMessageRequest({operationId:a.operationId,command:a.command.name,paramsHash:a.paramsHash,runtimeEpoch:a.dispatchEpoch,requestMessageId:a.requestMessageId,requestHash:a.requestHash});s.mark("journal-accept");const v=I?E:f,y=decideMessageServiceRecovery(l.snapshot,a,v,I);if("already_terminal"===y)return void await d();if(!CORE_RECOVERY_DECISIONS.has(y))return await this.markOutcomeUnknownIfMutable(f),await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(a,new RuntimeCommandError("OUTCOME_UNKNOWN",`Types recovery policy requires ${y}; this runtime cannot safely replay Core`),(new Date).toISOString())}),void await d();if(await this.verifier.refresh(),s.mark("verifier-refresh"),!i)return I?await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(a,new RuntimeCommandError("OUTCOME_UNKNOWN","retained started proof has no live execution grant"),(new Date).toISOString())}):await this.reject(a,"invalid_execution_grant",t),void await d();this.verifier.verifyAndDecodeGrant(i)||await this.verifier.refresh(!0);const R=()=>c.abort();let _;o.addEventListener("abort",R,{once:!0});try{_=await verifyAndCreateMessageServiceCoreExecutionAttempt({request:a,executionGrant:i,runtimeEpoch:t.runtimeEpoch,lease:t,verifier:this.verifier,allowExpiredGrant:I,abortSignal:c.signal,resolvePorts:async()=>await this.ports.resolve({request:a,executionGrant:i,command:a.command,getLease:()=>this.currentLease(t),...this.journalLineage(),journalRecord:f,abortSignal:c.signal})})}catch(e){if(o.removeEventListener("abort",R),c.abort(),!(e instanceof RuntimeResourceResolutionError))throw e;if("transient"===e.kind)return;return I?(await this.markOutcomeUnknownIfMutable(f),await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(a,new RuntimeCommandError("OUTCOME_UNKNOWN","eager resources cannot be reconstructed after retained started proof"),(new Date).toISOString())})):await this.reject(a,"integrity_mismatch"===e.kind?"resource_integrity_mismatch":"resource_resolution_rejected",t),void await d()}if(!_.ok)return o.removeEventListener("abort",R),c.abort(),I?(await this.markOutcomeUnknownIfMutable(f),await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:failurePayload(a,new RuntimeCommandError("OUTCOME_UNKNOWN","execution grant cannot be revalidated after retained started proof"),(new Date).toISOString())})):await this.reject(a,"invalid_execution_grant",t),void await d();if(!p||"prepared"!==f.phase&&"started"!==f.phase||c.abort(),c.signal.aborted&&!I)return o.removeEventListener("abort",R),await this.reject(a,"cancelled_before_dispatch",t),void await d();s.mark("attempt"),I?this.currentLease(t):(await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),executionGrant:i,type:RUNTIME_MESSAGE_TYPES.started,payload:{...runtimeMessageBase(a),executionMode:"start",executionGrantHash:a.executionGrantHash,handlerIdentity:t.handlerIdentity,claimedAt:f.messageBinding?.acceptedAt??f.updatedAt}}),s.mark("append-started"));const w=this.dispatcher.dispatch(a.command,_.context);w.completion.catch(()=>{}),n(()=>{c.abort(),w.cancel()}),c.signal.aborted&&w.cancel();const g=this.forwardProgress(a,w.progress,t).catch(()=>{});let M,S=!1;try{if(("mcp/set"===a.method||"mcp/unset"===a.method)&&hasEffectReceiptHandoff(w)){const e=await w.effectCommitted;if(e){if(e.operationId!==a.operationId||e.method!==a.command.name)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core MCP receipt correlation mismatch");const o=runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:a.method,result:e.result}),n={schemaVersion:1,operationId:a.operationId,method:a.method,coreResultRef:`sha256:${o}`,coreResultHash:o};M=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,n),S=!0,await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(a),effectRecord:n,effectRecordHash:M,committedAt:e.committedAt}}),await w.acknowledgeEffectReceipt({operationId:a.operationId,method:a.command.name,effectRecordHash:M})}}const o=await w.completion;await g,s.mark("core");const n=await this.journal.get(a.operationId);if(s.mark("journal-after-core"),!n||!isCommittedCorePhase(n.phase))throw new RuntimeCommandError("OUTCOME_UNKNOWN","Core completed without a committed local journal record");const r=n.updatedAt;let i,c;if("query"!==RUNTIME_INVOCATION_RECOVERY_CLASSES[a.method]){const e=isAgentProjectionMethod(a.method),c=w.getSnapshot().projection,l=n.agentProjectionIntent??n.agentProjectionPreparationFailure;if(e&&!l)throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent effect committed without an atomic projection preparation record");if(!M){const e=l?.coreResultHash??runtimeMessageHash(RUNTIME_HASH_DOMAINS.coreResult,{method:a.method,result:o}),i=n.agentProjectionIntent?.projectionRevision??(methodRequiresProjectionRevision(a.method)?c?.revision??`${Math.max(0,n.revision)}`:void 0),d={schemaVersion:1,operationId:a.operationId,method:a.method,coreResultRef:`sha256:${e}`,coreResultHash:e,...i?{projectionRevision:i}:{}};M=runtimeMessageHash(RUNTIME_HASH_DOMAINS.effectRecord,d),S=!0,await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.effectCommitted,payload:{...runtimeMessageBase(a),effectRecord:d,effectRecordHash:M,committedAt:r}}),s.mark("append-effect")}let u=e?await awaitRuntimeProjection(w):c;if("session/set_model"===a.method&&"synced"!==u?.state&&(u=await this.projectSessionModelAfterEffect(a,_.context,n,u),s.mark("set-model-projection")),e&&n.agentProjectionIntent&&(!u||u.revision!==n.agentProjectionIntent.projectionRevision))throw new RuntimeCommandError("OUTCOME_UNKNOWN","agent projection receipt revision mismatch");if("pending"===u?.state)return void await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionPending,payload:{...runtimeMessageBase(a),revision:u.revision}});if("synced"===u?.state&&await this.publishProjectionSynced(a,t,{...u,state:"synced"}),"blocked"===u?.state){await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.projectionBlocked,payload:{...runtimeMessageBase(a),revision:u.revision,errorCode:"PROJECTION_BLOCKED"}});const e=RUNTIME_ERROR_DESCRIPTORS.PROJECTION_BLOCKED;return await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:{...runtimeMessageBase(a),outcome:"projection_blocked",response:{jsonrpc:"2.0",id:a.request.id,error:{code:e.jsonRpcCode,message:"PROJECTION_BLOCKED",data:{code:"PROJECTION_BLOCKED",operationId:a.operationId,effectState:"committed",retryMode:e.retryMode}}},effectRecordHash:M,completedAt:r}}),void await d()}i=e?u?.platformAgentId:void 0}try{c=sanitizeRuntimeServiceResult(a.method,o,_.context,i)}catch(e){throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed Core result cannot be mapped safely",void 0,{cause:e})}const l=runtimeMessageHash(RUNTIME_HASH_DOMAINS.serviceResult,{method:a.method,result:c});S=!0;const u=await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:{...runtimeMessageBase(a),outcome:"succeeded",response:{jsonrpc:"2.0",id:a.request.id,result:c},serviceResultHash:l,...M?{effectRecordHash:M}:{},completedAt:r}});if(s.mark("append-final"),this.observeRequestTerminalAccepted(e.deliveryId,u.message),("mcp/set"===a.method||"mcp/unset"===a.method)&&hasEffectReceiptHandoff(w)&&"function"==typeof w.acknowledgeTerminalReceipt){if(!M)throw new RuntimeCommandError("OUTCOME_UNKNOWN","MCP terminal response lacks its committed effect hash");await w.acknowledgeTerminalReceipt({operationId:a.operationId,method:a.command.name,effectRecordHash:M,terminalMessageId:u.message.message_id})}await d(),this.observeRequestAcknowledged(e.deliveryId,u.message)}catch(e){const o=await this.journal.get(a.operationId);if(o&&isCommittedCorePhase(o.phase)&&S)throw e;const n=!o||!isCommittedCorePhase(o.phase)||e instanceof RuntimeCommandError&&"OUTCOME_UNKNOWN"===e.code?e:new RuntimeCommandError("OUTCOME_UNKNOWN","Core effect committed before terminal mapping",void 0,{cause:e});this.observeRequestFailure(a.operationId,a.method,n);const r=failurePayload(a,n,o?.updatedAt??(new Date).toISOString());await this.channel.append({operationId:a.operationId,lease:this.currentLease(t),type:RUNTIME_MESSAGE_TYPES.final,payload:r}),s.mark("append-terminal-failure"),await d()}finally{s.finish(),c.abort(),o.removeEventListener("abort",R)}}async handlePoison(e,t,o){this.currentLease(o);(await this.poisonJournal.record({delivery:e,reason:durablePoisonReason(t),limit:this.options.poisonDeliveryLimit??3})).terminal&&await this.channel.acknowledge({deliveryIds:[e.deliveryId],lease:this.currentLease(o)})}observeRequestTerminalAccepted(e,t){try{this.options.onRequestTerminalAccepted?.({deliveryId:e,message:t})}catch{}}observeRequestAcknowledged(e,t){try{this.options.onRequestAcknowledged?.({deliveryId:e,message:t})}catch{}}async forwardProgress(e,t,o){for await(const n of t)await this.channel.append({operationId:e.operationId,lease:this.currentLease(o),type:RUNTIME_MESSAGE_TYPES.progress,payload:{...runtimeMessageBase(e),phase:n.phase,progress:n}})}async reject(e,t,o){await this.channel.append({operationId:e.operationId,lease:this.currentLease(o),type:RUNTIME_MESSAGE_TYPES.final,payload:prestartFailurePayload(e,t)})}async markOutcomeUnknownIfMutable(e){"prepared"!==e.phase&&"started"!==e.phase||await this.journal.transition({operationId:e.operationId,expectedRevision:e.revision,phase:"outcome_unknown",errorCode:"OUTCOME_UNKNOWN"})}async projectSessionModelAfterEffect(e,t,o,n){const r=Object.freeze({state:"pending",revision:n?.revision??`${Math.max(0,o.revision)}`}),a=e.command;if("session.setModel"!==a.name||"conversation"!==a.context.scope)return r;const i=t.sessionModelProjection;if(!i)return r;try{await i.project({conversationId:a.context.conversation.runtimeConversationUid,modelOverrideId:a.params.modelOverrideId});const e=Object.freeze({state:"synced",revision:r.revision});try{const t=await this.journal.get(o.operationId)??o;if("projection_committed"===t.phase&&"synced"===t.projectionReceipt?.snapshot.state)return Object.freeze({state:"synced",revision:t.projectionReceipt.snapshot.revision});await this.journal.transition({operationId:t.operationId,expectedRevision:t.revision,phase:"projection_committed",projectionSnapshot:e})}catch{}return e}catch(e){return this.observeDeliveryError(e),r}}async publishProjectionSynced(e,t,o){const n=this.currentLease(t),r=await this.channel.history({operationId:e.operationId,lease:n});if("found"===r.status&&"synced"===r.snapshot.projectionState)return;const a={...runtimeMessageBase(e),revision:o.revision};("found"!==r.status||"reserved"===r.snapshot.projectionState||"pending"!==r.snapshot.projectionState&&void 0===r.snapshot.projectionPending)&&await this.channel.append({operationId:e.operationId,lease:n,type:RUNTIME_MESSAGE_TYPES.projectionPending,payload:a}),await this.channel.append({operationId:e.operationId,lease:n,type:RUNTIME_MESSAGE_TYPES.projectionSynced,payload:{...a,...o.platformAgentId?{platformAgentId:o.platformAgentId}:{}}})}journalLineage(){return this.options.getJournalLineage?.()??{journalStoreId:this.options.journalStoreId,journalGeneration:this.options.journalGeneration}}currentLease(e){const t=this.getLease();if(!t||t.instanceId!==e.instanceId||t.leaseId!==e.leaseId||t.runtimeEpoch!==e.runtimeEpoch||t.handlerIdentity!==e.handlerIdentity||!Number.isFinite(Date.parse(t.leaseExpiresAt))||Date.parse(t.leaseExpiresAt)<=Date.now())throw new RuntimeCommandError("INSTANCE_EPOCH_MISMATCH","runtime message lease changed");return t}}function durablePoisonReason(e){const t=e.replace(/[^a-z0-9_:.-]+/gi,"_").replace(/^_+|_+$/g,"").slice(0,256);if(!t)throw new Error("poison reason is empty");return t}function isCommittedCorePhase(e){return"effect_committed"===e||"projection_committed"===e}const CORE_RECOVERY_DECISIONS=new Set(["execute","resume","replay_effect_committed","resume_projection","publish_committed_result","publish_projected_result","publish_terminal_result"]);function decideMessageServiceRecovery(e,t,o,n){const r=RUNTIME_INVOCATION_PARITY_ENTRIES.find(e=>e.acpMethod===t.method),a=e.terminal?"terminal":"blocked"===e.projectionState?"projection_blocked":"synced"===e.projectionState?"projection_synced":"pending"===e.projectionState?"projection_pending":e.effectCommitted?"runtime_committed":n?"running":"queued",i="failed"===o?.phase||"outcome_unknown"===o?.phase?"terminal":o?.phase,s=o&&o.runtimeEpoch===t.dispatchEpoch&&(void 0===o.messageBinding||o.messageBinding.requestMessageId===t.requestMessageId&&o.messageBinding.requestHash===t.requestHash)?"continuous":"discontinuous";return reduceRuntimeRecoveryPolicy({recoveryClass:r?.recoveryClass??RUNTIME_INVOCATION_RECOVERY_CLASSES[t.method],projectionRequired:"mcp/set"===t.method||"mcp/unset"===t.method||(r?.projectionRequired??!1),messageServicePhase:a,executionBoundaryCrossed:n,lineageContinuity:s,journalState:o?"available":"unavailable",...i?{journalPhase:i}:{}})}export function sanitizeRuntimeServiceResult(e,t,o,n){if("session/clear"===e)return serviceClearResult(t,o?.historyBoundary);if("session/set_model"===e)return serviceSetModelResult(t,o);if(!(e in SERVICE_CANONICAL_COMMAND_NAMES))return t;const r="cron/status"===e&&record(t)?{enabled:t.enabled,jobCount:t.jobCount,..."number"==typeof t.nextWakeAtMs?{nextWakeAtMs:t.nextWakeAtMs}:{}}:t;return mapCanonicalResultToService(e,r,{...n?{platformAgentId:n}:{},classifySkillOrigin:e=>e.bundled?{origin:"bundled",slug:e.slug}:{origin:"legacy_runtime",slug:e.slug},resolveMcpView:e=>({name:e.name,transport:e.transport,origin:"legacy_runtime",...void 0===e.connected?{}:{connected:e.connected},...void 0===e.connectionTimeoutMs?{}:{connectionTimeoutMs:e.connectionTimeoutMs}}),sanitizeDiagnostic:e=>/^[A-Z][A-Z0-9_]{0,63}$/.test(e)?e:"RUNTIME_EXECUTION_FAILED"})}function serviceClearResult(e,t){if(!record(e)||!0!==e.cleared||"string"!=typeof e.historyGeneration||!t||"message-service"!==t.kind)throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed clear result lacks its Message Service receipt");const o=t;if(!o.committedIntentId)throw new RuntimeCommandError("OUTCOME_UNKNOWN","Message Service clear receipt is unavailable");return{...e,clearIntentId:o.committedIntentId()}}function serviceSetModelResult(e,t){if(!record(e)||"string"!=typeof e.effectiveModelId||!t)throw new RuntimeCommandError("OUTCOME_UNKNOWN","committed set_model result is invalid");const o=t.verifiedMessageGrant?.claims.command;if(!o||"session.setModel"!==o.name||"conversation"!==o.context.scope)throw new RuntimeCommandError("OUTCOME_UNKNOWN","set_model Service target receipt is unavailable");const n=o.params.modelOverrideId;return{conversationId:o.context.conversation.runtimeConversationUid,...null===n?{defaultModelId:e.effectiveModelId}:{},modelOverrideId:n,effectiveModelId:e.effectiveModelId,effectState:"committed",projectionState:"synced",operationId:t.operationId}}function isAgentProjectionMethod(e){return"agent/create"===e||"agent/update"===e||"agent/delete"===e||"agent/applyTemplate"===e}function methodRequiresProjectionRevision(e){return isAgentProjectionMethod(e)||"session/set_model"===e}function record(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function sameLeaseBinding(e,t){return e.instanceId===t.instanceId&&e.leaseId===t.leaseId&&e.handlerIdentity===t.handlerIdentity&&e.runtimeEpoch===t.runtimeEpoch}function failurePayload(e,t,o){const n=t instanceof RuntimeCommandError?t.code:"RUNTIME_EXECUTION_FAILED",r=n in RUNTIME_ERROR_DESCRIPTORS?n:"RUNTIME_EXECUTION_FAILED",a="OUTCOME_UNKNOWN"===r||"INSTANCE_EPOCH_MISMATCH"===r?"outcome_unknown":"COMMAND_EXPIRED"===r?"expired":"OPERATION_CANCELLED"===r?"cancelled":"failed",i="outcome_unknown"===a?"OUTCOME_UNKNOWN":r,s=RUNTIME_ERROR_DESCRIPTORS[i],c="outcome_unknown"===a?"outcome_unknown":a;return{...runtimeMessageBase(e),outcome:a,response:{jsonrpc:"2.0",id:e.request.id,error:{code:s.jsonRpcCode,message:i,data:{code:i,operationId:e.operationId,effectState:c,retryMode:s.retryMode}}},completedAt:o}}function prestartFailurePayload(e,t){const o="invalid_execution_grant"===t?"INVALID_EXECUTION_GRANT":"unsupported_method"===t?"METHOD_NOT_SUPPORTED":"authority_mismatch"===t?"INSTANCE_EPOCH_MISMATCH":"execution_deadline_exceeded"===t?"COMMAND_EXPIRED":"cancelled_before_dispatch"===t?"OPERATION_CANCELLED":"resource_resolution_rejected"===t?"INVALID_INVOCATION":"RUNTIME_EXECUTION_FAILED",n=RUNTIME_ERROR_DESCRIPTORS[o in RUNTIME_ERROR_DESCRIPTORS?o:"RUNTIME_EXECUTION_FAILED"],r="COMMAND_EXPIRED"===o?"expired":"OPERATION_CANCELLED"===o?"cancelled":"failed";return{...runtimeMessageBase(e),outcome:r,response:{jsonrpc:"2.0",id:e.request.id,error:{code:n.jsonRpcCode,message:o,data:{code:o,operationId:e.operationId,effectState:r,retryMode:n.retryMode}}},completedAt:(new Date).toISOString()}}function abortable(e,t){return t?t.aborted?Promise.resolve():new Promise((o,n)=>{const r=()=>o();t.addEventListener("abort",r,{once:!0}),e.then(()=>{t.removeEventListener("abort",r),o()},e=>{t.removeEventListener("abort",r),n(e)})}):e}function delay(e,t){return new Promise(o=>{if(t?.aborted)return o();const n=()=>{clearTimeout(r),o()},r=setTimeout(()=>{t?.removeEventListener("abort",n),o()},e);t?.addEventListener("abort",n,{once:!0})})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{MsRuntimeMessageChannelAdapter}from"./ms-runtime-message-channel-adapter.js";export class MsRuntimeMessageChannelConsumerFactory{deps;constructor(e){this.deps=e}async create(e){return new MsRuntimeMessageChannelAdapter(e.dispatcher,
|
|
1
|
+
import{MsRuntimeMessageChannelAdapter}from"./ms-runtime-message-channel-adapter.js";import{decodeRuntimeAppendMessage,decodeRuntimeBrokerDelivery,decodeRuntimeOperationHistory,RuntimeMessageChannelProtocolError}from"./message-service-runtime-message-channel.js";export class MsRuntimeMessageChannelConsumerFactory{deps;constructor(e){this.deps=e}async create(e){let n;const s=new SdkRuntimeMessageChannel(()=>{if(!n)throw new Error("SDK runtime delivery consumer is not attached");return n}),t=new MsRuntimeMessageChannelAdapter(e.dispatcher,s,this.deps.verifier,this.deps.ports,this.deps.journal,()=>e.getLease(),{...this.deps.options,allowedMethods:this.deps.allowedMethods,journalStoreId:e.binding.journalStoreId,journalGeneration:e.binding.journalGeneration,...e.getBinding?{getJournalLineage:()=>{const n=e.getBinding();return{journalStoreId:n.journalStoreId,journalGeneration:n.journalGeneration}}}:{}});return n=this.deps.client.methods.consume({onDelivery:async(n,s)=>{const r=currentFencedLease(e,s.lease);await t.acceptDelivery(decodeRuntimeBrokerDelivery(n),r,s.signal)},onError:this.deps.options.onDeliveryError,readCount:this.deps.options.readCount,blockMs:this.deps.options.blockMs,idleDelayMs:this.deps.options.idleDelayMs,renewIntervalMs:this.deps.options.renewIntervalMs}),t.attachExternalDeliveryOwner(n),t}}class SdkRuntimeMessageChannel{consumer;constructor(e){this.consumer=e}async read(){throw new Error("MessageClient owns runtime delivery reads")}async renew(){throw new Error("MessageClient owns runtime delivery renewal")}async acknowledge(e){assertLeaseAvailable(e.lease),await this.consumer().acknowledge(e.deliveryIds)}async history(e){assertLeaseAvailable(e.lease);const n=await this.consumer().history(e.operationId);return"found"!==n.status?n:{status:"found",snapshot:decodeRuntimeOperationHistory(n.snapshot,e.operationId)}}async append(e){assertLeaseAvailable(e.lease);const n=await this.consumer().append(e.operationId,e.type,e.payload,e.executionGrant);if("created"===n.outcome||"duplicate"===n.outcome)return{disposition:"created"===n.outcome?"created":"reused",message:decodeRuntimeAppendMessage(n.message,e)};if(n.history){const s=findReconciledMessage(decodeRuntimeOperationHistory(n.history,e.operationId),e);if(s)return{disposition:"reused",message:s}}throw new RuntimeMessageChannelProtocolError("runtime append outcome is unknown after history reconcile",503,"OUTCOME_UNKNOWN")}}function currentFencedLease(e,n){const s=e.getLease(),t=e.getBinding?.()??e.binding;if(!s||s.instanceId!==n.instanceId||s.handlerIdentity!==n.handlerIdentity||s.runtimeEpoch!==n.runtimeEpoch||s.leaseId!==n.leaseId||s.leaseExpiresAt!==n.leaseExpiresAt||t.journalStoreId!==n.journalStoreId||t.journalGeneration!==n.journalGeneration||Date.parse(s.leaseExpiresAt)<=Date.now())throw new Error("runtime delivery lease fence changed");return s}function assertLeaseAvailable(e){if(!Number.isFinite(Date.parse(e.leaseExpiresAt))||Date.parse(e.leaseExpiresAt)<=Date.now())throw new Error("runtime delivery lease expired")}function findReconciledMessage(e,n){for(let s=e.messages.length-1;s>=0;s-=1)try{return decodeRuntimeAppendMessage(e.messages[s],n)}catch{}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class RuntimeAuthorityMessageBusLifecycle{authority;bus;logger;retryDelayMs;unsubscribeAuthority;retryTimer;queue=Promise.resolve();retryAttemptedKey;desired;activeKey;generation=0;started=!1;constructor(e){this.authority=e.authority,this.bus=e.bus,this.logger=e.logger,this.retryDelayMs=e.retryDelayMs??5e3}start(){this.started||(this.started=!0,this.unsubscribeAuthority=this.authority.subscribeAuthority(e=>this.reconcile(e)),this.reconcile(this.authority.authorityLease))}async stop(){if(!this.started)return;this.started=!1,this.generation++,this.desired=void 0,this.clearRetry(),this.unsubscribeAuthority?.(),this.unsubscribeAuthority=void 0;const e=this.queue.then(async()=>{this.activeKey&&(this.activeKey=void 0,await this.bus.disconnect())});this.queue=e.catch(()=>{}),await e}reconcile(e){this.started&&(this.generation++,this.desired=e??void 0,this.clearRetry(),this.retryAttemptedKey=void 0,e||this.logger?.info?.("[beeos-claw] MessageClient waiting for current runtime authority"),this.enqueue(this.generation,e??void 0))}enqueue(e,t){const i=this.queue.then(()=>this.apply(e,t));return this.queue=i.catch(()=>{}),i}async apply(e,t){if(!this.isCurrent(e,t))return;const i=t?leaseKey(t):void 0;if(this.activeKey!==i&&(this.activeKey&&(this.activeKey=void 0,await this.bus.disconnect()),t&&this.isCurrent(e,t))){try{await this.bus.connect(t)}catch(i){return void(this.isCurrent(e,t)&&(this.logger?.warn?.("[beeos-claw] MessageClient initial connect fenced; retrying only while runtime authority remains current: "+(i instanceof Error?i.message:String(i))),this.scheduleRetry(t)))}this.isCurrent(e,t)?this.activeKey=i:await this.bus.disconnect()}}scheduleRetry(e){const t=leaseKey(e);!this.retryTimer&&this.started&&this.desired&&leaseKey(this.desired)===t&&this.retryAttemptedKey!==t&&(this.retryAttemptedKey=t,this.retryTimer=setTimeout(()=>{this.retryTimer=void 0,this.started&&this.desired&&leaseKey(this.desired)===t&&(this.generation++,this.enqueue(this.generation,this.desired))},this.retryDelayMs),this.retryTimer.unref?.())}clearRetry(){this.retryTimer&&clearTimeout(this.retryTimer),this.retryTimer=void 0}isCurrent(e,t){return!(!this.started||e!==this.generation)&&(t?void 0!==this.desired&&leaseKey(this.desired)===leaseKey(t):void 0===this.desired)}}function leaseKey(e){return[e.instanceId,e.leaseId,e.runtimeEpoch,e.handlerIdentity].join("\0")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{parseAndValidateTemplatePackage}from"../acp-gateway/agent-template-package.js";import{HttpRuntimeOperationResourceResolver,RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{
|
|
1
|
+
import{RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}from"@beeos-ai/beeos-types/runtime";import{parseAndValidateTemplatePackage}from"../acp-gateway/agent-template-package.js";import{HttpRuntimeOperationResourceResolver,RuntimeResourceResolutionError}from"./runtime-operation-resource-resolver.js";import{MessageServiceConversationClearHttpAuthority}from"./message-service-conversation-clear-http-authority.js";import{MessageServiceConversationHistoryBoundary}from"./message-service-conversation-history-boundary.js";import{MessageServiceSessionModelHttpAuthority}from"./message-service-session-model-http-authority.js";import{MsRuntimeMessageChannelConsumerFactory}from"./ms-runtime-message-channel-consumer-factory.js";import{RuntimeJwksVerifier}from"./runtime-jwks-verifier.js";import{GatewayGrantJwksVerifier}from"./split-runtime-authorization-verifier.js";import{RUNTIME_MESSAGE_CHANNEL_REVISION,RUNTIME_PARITY_MANIFEST}from"./runtime-parity-manifest.js";import{intersectRuntimeServiceMethodsWithBackendCapabilities,loadRuntimeServiceContractIdentity,runtimeServiceContractRequirement}from"./runtime-service-contract-compatibility.js";const CONTRACT_ARTIFACT_BLOCKER=Object.freeze({id:"contract_artifact_mismatch",owner:"backend",requiredContract:"frozen Types manifest and Service wire artifacts"}),TRANSPORT_BLOCKER=Object.freeze({id:"message_channel_transport",owner:"backend",requiredContract:`${RUNTIME_MESSAGE_CHANNEL_REVISION} HTTP delivery/history/append authority`}),JWKS_BLOCKER=Object.freeze({id:"gateway_grant_jwks",owner:"backend",requiredContract:"Purpose-bound Gateway runtime execution-grant JWKS proxy"}),POISON_JOURNAL_BLOCKER=Object.freeze({id:"poison_journal",owner:"openclaw_runtime_client",requiredContract:"Durable bounded runtime poison-delivery journal"}),grantAdapterOptions=new WeakMap;export class AgentGatewayGrantJwksReadinessAdapter{constructor(e){grantAdapterOptions.set(this,{agentGatewayUrl:new URL(e.agentGatewayUrl).toString()})}}export class AgentGatewaySplitJwksReadinessAdapter extends AgentGatewayGrantJwksReadinessAdapter{}const transportOptions=new WeakMap;export class RuntimeServiceTransportReadinessAdapter{constructor(e){transportOptions.set(this,{messageService:e.messageService,messageClient:e.messageClient,scopedDeliveryKey:e.scopedDeliveryKey,agentGatewayUrl:new URL(e.agentGatewayUrl).toString(),boundAgentFetch:e.boundAgentFetch})}}const CAPABILITY_BLOCKERS=Object.freeze(RUNTIME_PARITY_MANIFEST.filter(e=>"blocked"===e.status).map(e=>Object.freeze({method:e.method,owner:e.blockerOwner,requiredContract:e.contractRevision})));export async function createRuntimeServiceProductionCandidate(e){try{loadRuntimeServiceContractIdentity()}catch{return{status:"unavailable",blockers:Object.freeze([{...CONTRACT_ARTIFACT_BLOCKER,requiredContract:contractArtifactRequirement()}])}}if(!e.poisonJournal)return{status:"unavailable",blockers:Object.freeze([POISON_JOURNAL_BLOCKER])};let t;if(e.authorizationAdapter)try{t=await probeGatewayGrantJwks(e.authorizationAdapter)}catch{}const r=e.transportAdapter?transportOptions.get(e.transportAdapter):void 0;if(!r?.messageClient)return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER,...t?[]:[JWKS_BLOCKER]])};if(!t)return{status:"unavailable",blockers:Object.freeze([JWKS_BLOCKER])};try{const n=await readMessageServiceWithBoundedRetry(r.messageService,e.messageServiceReadyRetryBackoffsMs),a=runtimeMethods(),i=e.attemptPorts??runtimeMessageAttemptPorts(r,n.serviceUrl);return{status:"ready",blockers:[],capabilityBlockers:CAPABILITY_BLOCKERS,serviceMethods:a,factory:new MsRuntimeMessageChannelConsumerFactory({client:r.messageClient,verifier:t,ports:i,journal:e.commandJournal,allowedMethods:new Set(a),options:{poisonJournal:e.poisonJournal,...e.onConsumerDeliveryError?{onDeliveryError:e.onConsumerDeliveryError}:{},...e.onConsumerRequestTerminalAccepted?{onRequestTerminalAccepted:e.onConsumerRequestTerminalAccepted}:{},...e.onConsumerRequestAcknowledged?{onRequestAcknowledged:e.onConsumerRequestAcknowledged}:{},...e.onConsumerRequestFailure?{onRequestFailure:e.onConsumerRequestFailure}:{}}}),...void 0===e.attemptPorts?{resourceBindingContractRevision:RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION}:{}}}catch{return{status:"unavailable",blockers:Object.freeze([TRANSPORT_BLOCKER])}}}async function probeGatewayGrantJwks(e){const t=grantAdapterOptions.get(e);if(!t)throw new Error("untrusted Gateway grant readiness adapter");const r=t.agentGatewayUrl.replace(/\/+$/,""),n=new GatewayGrantJwksVerifier(new RuntimeJwksVerifier(`${r}/.well-known/runtime-execution-grant-jwks.json`));return await n.refresh(!0),n}const MESSAGE_SERVICE_READY_RETRY_BACKOFFS_MS=[1e3,2e3,4e3],MESSAGE_SERVICE_READY_OVERALL_BUDGET_MS=15e3;async function readMessageServiceWithBoundedRetry(e,t=MESSAGE_SERVICE_READY_RETRY_BACKOFFS_MS,r=MESSAGE_SERVICE_READY_OVERALL_BUDGET_MS){const n=Date.now()+r,a=t.length+1;let i;for(let s=1;s<=a;s++){const o=n-Date.now();if(o<=0)throw i??new Error(`Message Service readiness probe exceeded its ${r}ms startup budget`);try{return await withDeadline(e.ready(),o,()=>new Error(`Message Service readiness probe attempt ${s}/${a} timed out after ${o}ms`))}catch(e){if(i=e,s===a)throw e;const r=Math.min(t[s-1],Math.max(0,n-Date.now()));r>0&&await delay(r)}}throw i??new Error("Message Service readiness probe exited without a result")}function withDeadline(e,t,r){return new Promise((n,a)=>{const i=setTimeout(()=>a(r()),t);e.then(e=>{clearTimeout(i),n(e)},e=>{clearTimeout(i),a(e)})})}const delay=e=>new Promise(t=>setTimeout(t,e));export function runtimeMessageAttemptPorts(e,t){const r=new MessageServiceConversationClearHttpAuthority({messageServiceUrl:t,fetchAuthorized:async(t,r)=>await e.messageService.fetch(t,r)});return{async resolve({request:n,command:a,executionGrant:i,getLease:s,journalStoreId:o,journalGeneration:c,journalRecord:u,abortSignal:d}){const m="skills.install"===a.name||"agent.applyTemplate"===a.name?a.params.artifact:void 0,l="mcp.set"===a.name?a.params.definition:void 0;if((m||l)&&(n.resourceBindingContractRevision!==RUNTIME_RESOURCE_BINDING_CONTRACT_REVISION||!n.resourceBindings))throw new RuntimeResourceResolutionError("resolution_rejected","resource-bound Service delivery requires the Types 4.1 binding carrier");const p=new HttpRuntimeOperationResourceResolver({gatewayUrl:e.agentGatewayUrl,operationId:n.operationId,requestMessageId:n.requestMessageId,instanceId:n.instanceId,paramsHash:n.paramsHash,bindings:n.resourceBindings??[],getAuthorization:()=>{const e=s();return{executionGrant:i,runtimeLeaseCredential:e.runtimeLeaseCredential,fence:deliveryFence(n,e,o,c)}},fetchAuthorized:async(t,r)=>await e.boundAgentFetch(t,r??{}),parseSkillArtifact:e=>parseAndValidateTemplatePackage(Buffer.from(e)),signal:d});if("prepared"!==u.phase&&"started"!==u.phase||(m&&await p.readVerifiedArtifact(m),l&&await p.resolveMcpDefinition(l)),"session.setModel"===a.name)return{resources:p,sessionModelProjection:{project:async({conversationId:r,modelOverrideId:a})=>{const i=s(),o=new MessageServiceSessionModelHttpAuthority({messageServiceUrl:t,fetchAuthorized:async(t,r)=>await e.messageService.fetchRuntimeChannel(t,{runtimeLeaseCredential:i.runtimeLeaseCredential},r)});return await o.project({conversationId:r,operationId:n.operationId,requestMessageId:n.requestMessageId,requestHash:n.requestHash,modelOverrideId:a,runtimeLeaseCredential:i.runtimeLeaseCredential})}}};if("session.clear"!==a.name)return{resources:p};const R=a.context.conversation,y=s();return{resources:p,historyBoundary:new MessageServiceConversationHistoryBoundary({operationId:n.operationId,requestMessageId:n.requestMessageId,expectedGeneration:await r.currentGeneration(R.runtimeConversationUid),fence:deliveryFence(n,y,o,c),authority:r})}}}}function runtimeMethods(){return[...intersectRuntimeServiceMethodsWithBackendCapabilities(RUNTIME_PARITY_MANIFEST.filter(e=>"ready"===e.status).map(e=>e.method))]}function contractArtifactRequirement(){try{return runtimeServiceContractRequirement()}catch{return CONTRACT_ARTIFACT_BLOCKER.requiredContract}}function deliveryFence(e,t,r,n){if(t.handlerIdentity!==e.runtimeHandlerIdentity||t.runtimeEpoch!==e.dispatchEpoch)throw new Error("runtime delivery fence does not match the retained request");return{claimId:e.requestMessageId,handlerIdentity:t.handlerIdentity,runtimeEpoch:t.runtimeEpoch,fencingToken:t.runtimeEpoch,journalStoreId:r,journalGeneration:n}}
|
package/openclaw.plugin.json
CHANGED
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
},
|
|
151
151
|
"messageBus": {
|
|
152
152
|
"type": "object",
|
|
153
|
-
"description": "Message SDK configuration. Connects to Message Service
|
|
153
|
+
"description": "Message SDK configuration. Connects to Message Service for A2A messaging and real-time communication.",
|
|
154
154
|
"properties": {
|
|
155
155
|
"enabled": {
|
|
156
156
|
"type": "boolean",
|
|
@@ -160,10 +160,6 @@
|
|
|
160
160
|
"type": "string",
|
|
161
161
|
"description": "Message Service REST URL (e.g. http://localhost:8089). Also readable from MESSAGE_SERVICE_URL env var."
|
|
162
162
|
},
|
|
163
|
-
"url": {
|
|
164
|
-
"type": "string",
|
|
165
|
-
"description": "Override: Centrifugo WebSocket endpoint. Normally obtained from Message Service token response. Also readable from CENTRIFUGO_URL env var."
|
|
166
|
-
},
|
|
167
163
|
"principalId": {
|
|
168
164
|
"type": "string",
|
|
169
165
|
"description": "Override: messaging principal ID. Normally resolved from Gateway. Also readable from BEEOS_PRINCIPAL_ID env var."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beeos-claw",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.17",
|
|
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>",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@beeos-ai/beeos-types": "4.2.0",
|
|
65
65
|
"@beeos-ai/bridge-client": "^0.1.0",
|
|
66
|
-
"@beeos-ai/message-sdk": "
|
|
66
|
+
"@beeos-ai/message-sdk": "2.0.0",
|
|
67
67
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
68
68
|
"adm-zip": "^0.6.0",
|
|
69
69
|
"ws": "^8.18.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export class MessageServiceRecoveryScanner{client;imInbound;logger;maxConversationsPerScan;maxMessagesPerConversation;pageLimit;agentIdentity="";stopped=!0;scanInFlight=!1;constructor(e){this.client=e.client,this.imInbound=e.imInbound,this.logger=e.logger,this.maxConversationsPerScan=Math.max(1,e.maxConversationsPerScan??100),this.maxMessagesPerConversation=Math.max(1,e.maxMessagesPerConversation??20),this.pageLimit=Math.max(20,Math.min(500,e.pageLimit??200))}start(e){e?(this.agentIdentity=e,this.stopped?(this.stopped=!1,this.trigger("startup")):this.trigger("identity-refresh")):this.logger.warn?.("[beeos-claw] MS recovery scanner not started: empty agent identity")}stop(){this.stopped=!0}trigger(e){!this.stopped&&this.agentIdentity&&this.scan(e)}async scan(e){if(this.scanInFlight)return;this.scanInFlight=!0;const t=Date.now();try{const s=await this.listOpenConversations();let n=0;for(const e of s)n+=await this.recoverConversation(e);this.logger.info?.(`[beeos-claw] MS recovery scan complete reason=${e} conversations=${s.length} recovered=${n} elapsed_ms=${Date.now()-t}`)}catch(t){this.logger.warn?.(`[beeos-claw] MS recovery scan failed reason=${e}: ${t instanceof Error?t.message:String(t)}`)}finally{this.scanInFlight=!1}}async listOpenConversations(){const e=[];let t;do{const s=await this.client.identities.conversations(this.agentIdentity,{state:"open",limit:Math.min(this.maxConversationsPerScan-e.length,100),cursor:t});e.push(...s.conversations),t=s.hasMore?s.nextCursor:void 0}while(t&&e.length<this.maxConversationsPerScan);return e}async recoverConversation(e){const t=await this.loadUnhandledMessages(e.id);if(0===t.length)return 0;let s=0;for(const n of t){if(s>=this.maxMessagesPerConversation)break;"chat_message"===n.type&&(n.sender!==this.agentIdentity&&this.isAddressedToThisAgent(n)&&(this.logger.info?.(`[beeos-claw] MS recovery dispatch conversation=${e.id} message_id=${n.id}`),await this.imInbound.onChatMessage(n),s++))}return s}async loadUnhandledMessages(e){const t=[];let s;for(;t.length<this.maxMessagesPerConversation;){const n=this.maxMessagesPerConversation-t.length,a=await this.listUnhandledMessagePage(e,s,Math.min(this.pageLimit,n));if(t.push(...a.messages),s=a.hasMore?a.nextCursor:void 0,!s)break}return s&&this.logger.warn?.(`[beeos-claw] MS recovery scan capped unhandled messages conversation=${e} count=${t.length}`),t}async listUnhandledMessagePage(e,t,s){const n=new URLSearchParams;n.set("limit",String(s)),n.set("unhandled_by",this.agentIdentity),t&&n.set("cursor",t);const a=await this.client.request("GET",`/api/v2/conversations/${encodeURIComponent(e)}/messages?${n.toString()}`);return{messages:a.messages.map(rawV2MessageToSdkMessage),nextCursor:a.next_cursor,hasMore:a.has_more??!1}}isAddressedToThisAgent(e){const t=e.content??{},s=t.metadata&&"object"==typeof t.metadata?t.metadata:void 0,n="string"==typeof s?.delivery_principal?s.delivery_principal:"";return!n||n===this.agentIdentity}}function rawV2MessageToSdkMessage(e){return{id:String(e.id??""),type:String(e.type??""),sender:String(e.sender??""),conversationId:"string"==typeof e.conversation_id?e.conversation_id:void 0,content:e.content,replyTo:"string"==typeof e.reply_to?e.reply_to:void 0,createdAt:"string"==typeof e.created_at?e.created_at:void 0,updatedAt:"string"==typeof e.updated_at?e.updated_at:void 0,body:"string"==typeof e.body?e.body:void 0,parts:e.parts,state:"string"==typeof e.state?e.state:void 0,stopReason:"string"==typeof e.stop_reason?e.stop_reason:void 0}}
|