beeos-claw 0.6.6 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
import{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";function asString(t){if("string"==typeof t){return t.trim()||void 0}}function isRecord(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function normalizeText(t){if("string"==typeof t)return normalizeTransportText(t.replace(/\r\n/g,"\n").replace(/\r/g,"\n"))}const DEDUP_HASH_THRESHOLD=256;function dedupKey(t,...e){const s=`${t}:${e.join(":")}`;if(s.length<=256)return s;return`${t}:h:${createHash("sha256").update(s).digest("hex").slice(0,24)}`}function toISO(t){if(!(null==t||t<=0))try{return new Date(t).toISOString()}catch{return}}function resolveToolArgs(t){const e=t.arguments??t.args??t.input;if(isRecord(e))return e;if("string"==typeof e){const t=e.trim();if(t.startsWith("{")||t.startsWith("["))try{return JSON.parse(t)}catch{}}return{}}export function resolveTurnId(t){if(t.currentRunId)return t.currentRunId;const e=t.pendingPromptId;return void 0!==e?"string"==typeof e?`req:${e}`:`req:${String(e)}`:void 0}export function roleFromSessionUpdate(t){const e=t.sessionUpdate;if(e)return"user_message_chunk"===e?"user":"system_message_chunk"===e?"system":e.startsWith("agent_")||e.startsWith("tool_")?"assistant":"history_turn"===e?"user"===t.role?"user":"system"===t.role?"system":"assistant":void 0}export function recordTurnIdForRole(t,e,s){if(!s)return;const o=roleFromSessionUpdate(e);o&&(t.lastTurnIdByRole||(t.lastTurnIdByRole={}),t.lastTurnIdByRole[o]=s)}export function recordToolCallTurnForUpdate(t,e,s){s&&"tool_call"===e.sessionUpdate&&e.toolCallId&&(t.toolCallTurns??=new Map,t.toolCallTurns.set(e.toolCallId,{turnId:s,..."string"==typeof e.name&&e.name?{toolName:e.name}:{}}))}export class AcpGatewayEvents{state;transport;config;logger;writeObs;completePrompt;failPrompt;interruptPrompt;schedulePromptTimeout;adoptBackgroundMutation;abortGatewayRun;maybeCompletePromptFromChatState;agentFetchFn;getMessageClient;getMsgPrincipalId;gatewayRequest;getCanvasState;cronChannelCache;constructor(t){this.state=t.state,this.transport=t.transport,this.config=t.config,this.logger=t.logger,this.writeObs=t.writeObs,this.completePrompt=t.completePrompt,this.failPrompt=t.failPrompt,this.interruptPrompt=t.interruptPrompt,this.schedulePromptTimeout=t.schedulePromptTimeout,this.adoptBackgroundMutation=t.adoptBackgroundMutation,this.abortGatewayRun=t.abortGatewayRun,this.maybeCompletePromptFromChatState=t.maybeCompletePromptFromChatState,this.agentFetchFn=t.agentFetch,this.getMessageClient=t.getMessageClient,this.getMsgPrincipalId=t.getMsgPrincipalId,this.gatewayRequest=t.gatewayRequest,this.getCanvasState=t.getCanvasState,this.cronChannelCache=t.cronChannelCache}observeLifecycle(t,e,s){observeLifecyclePromise(t,this.logger,{component:"gateway-events",action:e,sessionId:s.sessionId,gatewaySessionKey:s.gatewaySessionKey})}touchStreamActivity(t){t.lastStreamActivityTs=Date.now(),this.resetStreamIdleTimer(t)}resetStreamIdleTimer(t){this.state.clearStreamIdleTimer(t);const e=this.config.streamIdleTimeoutMs??DEFAULT_STREAM_IDLE_TIMEOUT_MS,s=this.config.streamStaleTimeoutMs??DEFAULT_STREAM_STALE_TIMEOUT_MS;t.streamIdleTimer=setTimeout(()=>{if(!t.lastStreamActivityTs)return;const e=Date.now()-t.lastStreamActivityTs;e>s&&(this.logger.warn?.(`[gateway-events] Stream stale timeout sessionId=${t.sessionId} stale=${e}ms`),this.observeLifecycle(this.interruptPrompt(t,classifyAgentRuntimeFailure({raw:`Stream stale timeout after ${e}ms`,source:"live_gateway",runtimeKind:"openclaw"})),"stream_stale_timeout",t))},e),"object"==typeof t.streamIdleTimer&&"unref"in t.streamIdleTimer&&t.streamIdleTimer.unref()}sendTurnUpdate(t,e){const s=resolveTurnId(t);return s&&void 0===e._turnId&&(e._turnId=s),recordTurnIdForRole(t,e,s),recordToolCallTurnForUpdate(t,e,s),!!t.sessionLoadInProgress||this.transport.sendSessionUpdate(t.sessionId,e,{turnId:s})}sendToolCallSpacer(t,e){const s={sessionUpdate:"agent_message_chunk",content:{type:"text",text:"\n"},...e?{timestamp:e}:{}};this.sendTurnUpdate(t,s),t.assistantMq?.push(s)}resolveToolCallId(t,e,s){const o=asString(e.toolCallId)??asString(e.tool_call_id)??asString(e.callId)??asString(e.id);if(o){const e=this.state.toolCallIdMap.get(o);return e?(t.sessionId&&this.state.toolCallToSessionMap.set(e,t.sessionId),e):(this.state.toolCallIdMap.set(o,o),t.sessionId&&this.state.toolCallToSessionMap.set(o,t.sessionId),o)}const n=`tc_${t.currentRunId??"unknown"}_${this.state.nextToolCallSeq++}`;return t.sessionId&&this.state.toolCallToSessionMap.set(n,t.sessionId),n}handleChatEvent(t){const e=(t.runId?this.state.promptsByRunId.get(t.runId):void 0)??this.state.findSessionByKey(t.sessionKey);if(!e)return;if(!this.state.isCurrentStreamOwner(e,t.runId)){const s=!e.currentRunId&&void 0===e.pendingPromptId;if(!t.runId||!s||this.state.isRunCompleted(t.runId))return;if(!this.adoptBackgroundRun(e,t.runId))return}e.sessionId;const s=t.message;if(s){this.touchStreamActivity(e);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(e.hasAgentThinkingStream)continue;const t=normalizeText(n.thinking)??normalizeText(n.text);if(!t)continue;const s=dedupKey("chat:thinking",t);if(e.chatReplayDedupKeys.has(s))continue;e.chatReplayDedupKeys.add(s);const o={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:t},...i?{timestamp:i}:{}};this.sendTurnUpdate(e,o)&&e.assistantMq?.push(o);continue}if("toolCall"===a){if(this.sendToolCallSpacer(e,i),e.hasAgentToolStartStream)continue;const t=this.resolveToolCallId(e,n,"start"),s=asString(n.name)??asString(n.toolName)??asString(n.tool_name)??"tool",o=resolveToolArgs(n),a=dedupKey("chat:tool_start",t,JSON.stringify(o));if(e.chatReplayDedupKeys.has(a))continue;e.chatReplayDedupKeys.add(a);const r=buildToolCallPayload({toolCallId:t,toolName:s,args:o,timestamp:i});this.sendTurnUpdate(e,r)&&e.assistantMq?.push(r);continue}if("toolResult"===a){if(this.sendToolCallSpacer(e,i),e.hasAgentToolResultStream)continue;const t=this.resolveToolCallId(e,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",t,a);if(e.chatReplayDedupKeys.has(r))continue;e.chatReplayDedupKeys.add(r),this.emitNormalizedToolResult(e,{toolCallId:t,toolName:s,rawText:a,rawResult:o,timestamp:i}).catch(t=>{this.logger.warn?.(`[gateway-events] Tool result normalization failed: ${t instanceof Error?t.message:t}`)});continue}if("text"===a&&n.text){if(e.hasAgentAssistantStream||e.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:e.sessionId,runId:t.runId,cumulative:s});continue}const a=dedupKey("chat:text",s);if(e.chatReplayDedupKeys.has(a))continue;e.chatReplayDedupKeys.add(a);const r=e.chatAssistantTextSoFar??"";let l;if(e.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=${t.seq} runId=${t.runId} len=${l.length} prevLen=${r.length}`);const c={sessionUpdate:"agent_message_chunk",content:{type:"text",text:l},...i?{timestamp:i}:{}};this.sendTurnUpdate(e,c),e.assistantMq?.push(c)}}}}else"delta"===t.state&&this.touchStreamActivity(e);if(this.maybeCompletePromptFromChatState(e,t),!e.promptDone)if("final"===t.state)this.observeLifecycle(this.completePrompt(e,t.stopReason||"end_turn"),"chat_final",e);else if("aborted"===t.state||"cancelled"===t.state||"cancel"===t.state)this.observeLifecycle(this.completePrompt(e,"cancelled"),"chat_cancelled",e);else if("error"===t.state){const s=t.errorMessage||Errors.AgentError.message,o=classifyAgentRuntimeFailure({raw:s,source:"chat_state",runtimeKind:"openclaw"});"interrupted"===o.kind?this.observeLifecycle(this.interruptPrompt(e,o),"chat_interrupt",e):this.observeLifecycle(this.failPrompt(e,Errors.AgentError.rpcCode,o.publicMessage),"chat_failure",e)}}handleAgentEvent(t){const e=(t.runId?this.state.promptsByRunId.get(t.runId):void 0)??(t.sessionKey?this.state.findSessionByKey(t.sessionKey):void 0);if(!e)return;if(!this.state.isCurrentStreamOwner(e,t.runId)){const s=!e.currentRunId&&void 0===e.pendingPromptId;if(!t.runId||!s||this.state.isRunCompleted(t.runId))return;if(!this.adoptBackgroundRun(e,t.runId))return}t.runId&&e.cronJobId&&!this.state.runIdToCronJobId.has(t.runId)&&this.state.setRunCronJobId(t.runId,e.cronJobId);e.sessionId;this.touchStreamActivity(e);const s=toISO(t.ts),o=t=>{this.sendTurnUpdate(e,t),e.assistantMq?.push(t)};if("assistant"===t.stream){e.hasAgentAssistantStream=!0;const n=this.state.extractAgentAssistantDelta(e,t.data??{});if(n){const a=normalizeTransportText(n);if(a){const n=e.agentAssistantTextSoFar??a,i=classifyHeartbeatFilter(n);i.filtered?this.logHeartbeatSkip({source:"assistant-delta",reason:i.reason,sessionId:e.sessionId,runId:t.runId,cumulative:n,delta:a}):(this.logger.debug?.(`[gateway-events] agent.assistant delta seq=${t.seq} runId=${t.runId} len=${a.length} source=${t.data?.delta?"delta":"cumulative"}`),o({sessionUpdate:"agent_message_chunk",content:{type:"text",text:a},...s?{timestamp:s}:{}}))}}const a=[],i=t.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 t of a){if(!isRecord(t))continue;const n=asString(t.type);if("thinking"===n){e.hasAgentThinkingStream=!0;const o=normalizeText(t.thinking)??normalizeText(t.text);if(o){const t={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:o},...s?{timestamp:s}:{}};this.sendTurnUpdate(e,t)&&e.assistantMq?.push(t)}continue}if("toolCall"===n){this.sendToolCallSpacer(e,s),e.hasAgentToolStartStream=!0;const n=this.resolveToolCallId(e,t,"start"),a=asString(t.name)??asString(t.toolName)??asString(t.tool_name)??"tool",i=resolveToolArgs(t);o(buildToolCallPayload({toolCallId:n,toolName:a,args:i,timestamp:s}));continue}if("toolResult"===n){this.sendToolCallSpacer(e,s),e.hasAgentToolResultStream=!0;const n=this.resolveToolCallId(e,t,"result"),a=asString(t.name)??asString(t.toolName)??asString(t.tool_name)??"tool",i=t.result,r=normalizeText(t.text)??normalizeText(t.result)??(isRecord(i)||Array.isArray(i)?JSON.stringify(i,null,2):void 0);r&&this.emitNormalizedToolResult(e,{toolCallId:n,toolName:a,rawText:r,rawResult:i,timestamp:s,pushUpdate:o}).catch(t=>{this.logger.warn?.(`[gateway-events] Tool result normalization failed: ${t instanceof Error?t.message:t}`)});continue}}}else if("thinking"===t.stream){e.hasAgentThinkingStream=!0;const o=normalizeText(t.data?.delta)??normalizeText(t.data?.text);if(o){const t={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:o},...s?{timestamp:s}:{}};this.sendTurnUpdate(e,t)&&e.assistantMq?.push(t)}}else if("tool"===t.stream){const n=t.data,a="string"==typeof n?.phase?n.phase:void 0;if("start"===a){this.sendToolCallSpacer(e,s),e.hasAgentToolStartStream=!0;const t=this.resolveToolCallId(e,n,"start"),a=asString(n?.name)??asString(n?.toolName)??asString(n?.tool_name)??"tool",i=resolveToolArgs(n);o(buildToolCallPayload({toolCallId:t,toolName:a,args:i,timestamp:s}))}else if("result"===a)this.sendToolCallSpacer(e,s),e.hasAgentToolResultStream=!0,this.handleToolResult(e,t,s).catch(t=>{this.logger.warn?.(`[gateway-events] Tool result handling failed: ${t instanceof Error?t.message:t}`)});else{const t=n?.toolCall;if(t){this.sendToolCallSpacer(e,s),e.hasAgentToolStartStream=!0;const a=asString(t?.name)??asString(n?.name)??asString(n?.toolName)??"tool",i={...n,...t},r=this.resolveToolCallId(e,i,"start"),l=resolveToolArgs(i);o(buildToolCallPayload({toolCallId:r,toolName:a,args:l,timestamp:s}))}}}else if("lifecycle"===t.stream){const s=t.data,o="string"==typeof s?.phase?s.phase:void 0;if("end"===o){const t="string"==typeof s?.stopReason?s.stopReason:void 0;this.observeLifecycle(this.completePrompt(e,t||"end_turn"),"agent_final",e)}else if("cancelled"===o||"cancel"===o)this.observeLifecycle(this.completePrompt(e,"cancelled"),"agent_cancelled",e);else if("error"===o){const o=s?.error,n=o&&"object"==typeof o?o:void 0,a=("string"==typeof s?.message?s.message:void 0)??("string"==typeof o?o:void 0)??("string"==typeof n?.message?n.message:void 0)??("string"==typeof s?.reason?s.reason:void 0)??(n?JSON.stringify(n):void 0)??"gateway lifecycle error";this.logger.warn?.(`[gateway-events] lifecycle error runId=${t.runId} sessionKey=${t.sessionKey} message=${a} data=${JSON.stringify(s)}`);const i=classifyAgentRuntimeFailure({raw:a,source:"live_gateway",runtimeKind:"openclaw"});"interrupted"===i.kind?this.observeLifecycle(this.interruptPrompt(e,i),"agent_interrupt",e):this.observeLifecycle(this.failPrompt(e,Errors.AgentError.rpcCode,i.publicMessage),"agent_failure",e)}}}async handleToolResult(t,e,s){const o=e.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_${e.runId}_${e.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(t,{toolCallId:i,normalizeToolCallId:c,toolName:r,rawText:l,rawResult:d,timestamp:s})}async emitNormalizedToolResult(t,e){const s=e.pushUpdate??(e=>{this.sendTurnUpdate(t,e)&&t.assistantMq?.push(e)});await emitNormalizedToolResultForTurn({session:t,envelope:{toolCallId:e.toolCallId,toolName:e.toolName,rawText:e.rawText,rawResult:e.rawResult,timestamp:e.timestamp},normalizeToolCallId:e.normalizeToolCallId,turnId:resolveTurnId(t),config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:this.getCanvasState,sendUpdate:t=>(s(t),!0)})}adoptBackgroundRun(t,e){this.logger.debug?.(`[gateway-events] Adopting background run ${e} on session ${t.sessionId} (prev=${t.currentRunId})`);if(!this.adoptBackgroundMutation(t,e).ok)return this.observeLifecycle(this.abortGatewayRun(t.gatewaySessionKey,e),"background_run_conflict_abort",t),!1;t.currentRunId=e,t.promptDone=!1,this.state.promptsByRunId.set(e,t);const s=this.state.runIdToCronJobId.get(e);return s&&(t.cronJobId=s),t.assistantStreamStartedAt=Date.now(),t.lastStreamActivityTs=Date.now(),t.hasAgentAssistantStream=void 0,t.hasAgentThinkingStream=void 0,t.hasAgentToolStartStream=void 0,t.hasAgentToolResultStream=void 0,t.chatAssistantTextSoFar=void 0,t.agentAssistantTextSoFar=void 0,t.chatReplayDedupKeys?.clear(),t.presentedFileArtifactKeys=new Set,t.emittedToolResultKeys=new Set,t.assistantMq&&!t.assistantMq.closed||(t.assistantMq=new MessageQueue),this.schedulePromptTimeout(t),!0}handleCronEvent(t){this.transport.sendNotification("_beeos/cron_event",{payload:t});const e=t??{};"string"==typeof e.runId&&"string"==typeof e.jobId&&this.state.setRunCronJobId(e.runId,e.jobId),this.config.agentGatewayUrl&&this.agentFetchFn&&this.reportCronEventToAgentGateway(e),"added"===e.action&&this.maybeBackfillBeeosChannel(e),"finished"===e.action&&"string"==typeof e.summary&&e.summary&&this.publishCronResult(e)}async reportCronEventToAgentGateway(t){if(!this.config.agentGatewayUrl||!this.agentFetchFn)return;const e={...t},s="string"==typeof t.sessionKey?t.sessionKey:void 0;s&&null==e.sessionKey&&(e.sessionKey=s);const o=s?this.state.findSessionByKey(s)?.sessionId:void 0;if(o&&(e.sessionId=o),("string"!=typeof e.name||!e.name)&&"string"==typeof t.jobId&&t.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===t.jobId&&"string"==typeof s.name&&s.name){e.name=s.name;break}}catch(t){this.logger.debug?.(`[gateway-events] cron.list lookup failed: ${t instanceof Error?t.message:t}`)}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:e})})}catch(t){this.logger.warn?.(`[gateway-events] event report failed: ${t instanceof Error?t.message:t}`)}}async maybeBackfillBeeosChannel(t){const e="string"==typeof t.jobId?t.jobId:void 0;if(!e)return;if(!this.gatewayRequest)return;if(this.cronChannelCache?.get(e))return;let s;try{const t=await this.gatewayRequest("cron.list",{}),o=t?.jobs;if(Array.isArray(o))for(const t of o){if(!t||"object"!=typeof t)continue;const o=t;if(o.id===e){s=o;break}}}catch(t){return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel cron.list failed: ${t instanceof Error?t.message:t}`)}if(!s)return;const o="string"==typeof s.description?s.description:"";if(BEEOS_CHANNEL_TAG_RE.test(o)){const t=decodeBeeosChannelId(o);return void(t&&this.setCronChannelCacheLocal(e,t))}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=${e}`);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=${e}`);const c=encodeBeeosChannelTag(o,l);try{await this.gatewayRequest("cron.update",{id:e,patch:{description:c}}),this.setCronChannelCacheLocal(e,l),this.logger.info?.(`[gateway-events] backfilled beeos:channel for LLM-created cron job=${e} channel=${l}`)}catch(t){this.logger.warn?.(`[gateway-events] maybeBackfillBeeosChannel cron.update failed: ${t instanceof Error?t.message:t}`)}}setCronChannelCacheLocal(t,e){this.cronChannelCache&&this.cronChannelCache.set(t,e)}async publishCronResult(t){const e=this.getMessageClient?.();if(!e)return;const s=this.getMsgPrincipalId?.();if(!s)return;const o="string"==typeof t.jobId?t.jobId:void 0;if(!o)return;const n=t.summary;let a=this.cronChannelCache?.get(o);if(!a&&this.gatewayRequest)try{const t=await this.gatewayRequest("cron.list",{}),e=t?.jobs;if(Array.isArray(e))for(const t of e){if(!t||"object"!=typeof t)continue;const e=t;if(e.id===o){a=decodeBeeosChannelId(e.description);break}}}catch(t){this.logger.debug?.(`[gateway-events] cron.list fallback failed: ${t instanceof Error?t.message:t}`)}if(!a)return;const i=randomUUID();try{await e.messages.send({conversationId:a,id:i,type:"agent_reply",content:{text:n,cron_job_id:o,session_key:t.sessionKey??void 0}}),this.logger.info?.(`[gateway-events] published cron result to channel=${a} job=${o}`)}catch(t){const e=t instanceof Error?t.message:String(t);if(isChannelGoneError(t,e))return this.logger.info?.(`[gateway-events] origin channel gone (session likely deleted), skipping publish: channel=${a} job=${o} err=${e}`),void this.cronChannelCache?.delete(o);this.logger.warn?.(`[gateway-events] publishCronResult failed: ${e}`)}}logHeartbeatSkip(t){const e=previewSanitizedText(t.cumulative),s=void 0!==t.delta?previewSanitizedText(t.delta):void 0;this.logger.info?.(`[gateway-events] SKIP heartbeat ${t.source} reason=${t.reason} sessionId=${t.sessionId} runId=${t.runId??"n/a"} cumLen=${t.cumulative.length}`+(void 0!==t.delta?` deltaLen=${t.delta.length}`:"")+` cumulative="${e}"`+(void 0!==s?` delta="${s}"`:"")),this.writeObs({component:"gateway-events",domain:"lifecycle",name:"heartbeat_filter_skip",severity:"info",summary:`heartbeat ${t.source} drop (${t.reason})`,payload:{source:t.source,reason:t.reason,sessionId:t.sessionId,runId:t.runId,cumulativeLen:t.cumulative.length,cumulativePreview:e,...void 0!==t.delta?{deltaLen:t.delta.length,deltaPreview:s}:{}}})}}export function isChannelGoneError(t,e){const s=e.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(t)){const e=t.status??t.statusCode??t.code;if(404===e||410===e)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,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 +1 @@
|
|
|
1
|
-
import{formatFriendlyError}from"../errors/classify.js";export const INTERRUPTED_PUBLIC_MESSAGE="The previous run was interrupted before it completed.";export const TIMEOUT_PUBLIC_MESSAGE="The agent task timed out. Please retry.";export const GENERIC_RUNTIME_ERROR_PUBLIC_MESSAGE="The agent could not complete the request. Please retry.";const OPENCLAW_SESSION_LOCK_PATTERNS=[/^\s*(?:⚠️\s*)?agent failed before reply:\s*session file locked\b/i,/^\s*sessionwritelocktimeouterror\b/i,/^\s*OPENCLAW_SESSION_WRITE_LOCK\b/i],ABORT_PATTERNS=[/^\s*request was aborted\.?\s*$/i,/^\s*this operation was aborted\.?\s*$/i,/^\s*aborterror\b/i,/^\s*agent failed before reply:\s*.*\baborted\b/i],AGENT_LOST_PATTERNS=[/^\s*(?:agent failed before reply:\s*)?gateway disconnected\b/i,/^\s*agent lost\b/i,/^\s*no live owner\b/i,/^\s*stale live stream\b/i,/^\s*bridge_not_ready\b/i,/^\s*gateway not connected\b/i],TIMEOUT_PATTERNS=[/^\s*prompt timed out\b/i,/^\s*im runtime dispatch timed out\b/i,/^\s*gateway\b.*\btimed out\b/i,/^\s*deadline exceeded\b/i];function matchesAny(e,r){return r.some(r=>r.test(e))}function extractLockDiagnostics(e){const r=e.match(/\bpid=(\d+)\b/i)?.[1],t=e.match(/([^\s]+\.jsonl\.lock)\b/)?.[1];return{...r?{lockPid:r}:{},...t?{lockPath:t}:{}}}function asString(e){if(e instanceof Error)return e.message;if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}}export function classifyAgentRuntimeFailure(e){return detectAgentRuntimeFailure(e)??classifyProviderRuntimeFailure(e)}export function detectAgentRuntimeFailure(e){const r=e.runtimeKind??"openclaw",t=asString(e.raw).trim();
|
|
1
|
+
import{formatFriendlyError}from"../errors/classify.js";export const INTERRUPTED_PUBLIC_MESSAGE="The previous run was interrupted before it completed.";export const TIMEOUT_PUBLIC_MESSAGE="The agent task timed out. Please retry.";export const GENERIC_RUNTIME_ERROR_PUBLIC_MESSAGE="The agent could not complete the request. Please retry.";const OPENCLAW_SESSION_LOCK_PATTERNS=[/^\s*(?:⚠️\s*)?agent failed before reply:\s*session file locked\b/i,/^\s*sessionwritelocktimeouterror\b/i,/^\s*OPENCLAW_SESSION_WRITE_LOCK\b/i],ABORT_PATTERNS=[/^\s*request was aborted\.?\s*$/i,/^\s*this operation was aborted\.?\s*$/i,/^\s*aborterror\b/i,/^\s*agent failed before reply:\s*.*\baborted\b/i],AGENT_LOST_PATTERNS=[/^\s*(?:agent failed before reply:\s*)?gateway disconnected\b/i,/^\s*agent lost\b/i,/^\s*no live owner\b/i,/^\s*stale live stream\b/i,/^\s*bridge_not_ready\b/i,/^\s*gateway not connected\b/i],TIMEOUT_PATTERNS=[/^\s*prompt timed out\b/i,/^\s*im runtime dispatch timed out\b/i,/^\s*gateway\b.*\btimed out\b/i,/^\s*deadline exceeded\b/i],ALL_MODELS_FAILED_PATTERNS=[/^\s*(?:⚠️\s*)?(?:embedded\s+)?agent failed before reply:\s*all models failed\b/i,/^\s*all models failed\s*\(/i];function matchesAny(e,r){return r.some(r=>r.test(e))}function extractLockDiagnostics(e){const r=e.match(/\bpid=(\d+)\b/i)?.[1],t=e.match(/([^\s]+\.jsonl\.lock)\b/)?.[1];return{...r?{lockPid:r}:{},...t?{lockPath:t}:{}}}function asString(e){if(e instanceof Error)return e.message;if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}}export function classifyAgentRuntimeFailure(e){return detectAgentRuntimeFailure(e)??classifyProviderRuntimeFailure(e)}export function detectAgentRuntimeFailure(e){const r=e.runtimeKind??"openclaw",t=asString(e.raw).trim();if(e.requestAborted)return{kind:"interrupted",stopReason:"interrupted",publicMessage:INTERRUPTED_PUBLIC_MESSAGE,errorCode:"request_aborted",diagnosticMessage:t,source:e.source,runtimeKind:r};if(matchesAny(t,OPENCLAW_SESSION_LOCK_PATTERNS))return{kind:"interrupted",stopReason:"interrupted",publicMessage:INTERRUPTED_PUBLIC_MESSAGE,errorCode:"session_file_locked",diagnosticMessage:t,source:e.source,runtimeKind:r,...extractLockDiagnostics(t)};if(matchesAny(t,ABORT_PATTERNS))return{kind:"interrupted",stopReason:"interrupted",publicMessage:INTERRUPTED_PUBLIC_MESSAGE,errorCode:"request_aborted",diagnosticMessage:t,source:e.source,runtimeKind:r};if(matchesAny(t,AGENT_LOST_PATTERNS))return{kind:"interrupted",stopReason:"agent_lost",publicMessage:INTERRUPTED_PUBLIC_MESSAGE,errorCode:"agent_lost",diagnosticMessage:t,source:e.source,runtimeKind:r};if(matchesAny(t,TIMEOUT_PATTERNS))return{kind:"interrupted",stopReason:"timeout",publicMessage:TIMEOUT_PUBLIC_MESSAGE,errorCode:"gateway_timeout",diagnosticMessage:t,source:e.source,runtimeKind:r};if(matchesAny(t,ALL_MODELS_FAILED_PATTERNS)){const i=formatFriendlyError(t);return{kind:"error",stopReason:"error",publicMessage:i&&i!==t?i:GENERIC_RUNTIME_ERROR_PUBLIC_MESSAGE,errorCode:"provider_error",diagnosticMessage:t,source:e.source,runtimeKind:r}}}function classifyProviderRuntimeFailure(e){const r=e.runtimeKind??"openclaw",t=asString(e.raw).trim(),i=t.toLowerCase(),s=formatFriendlyError(t);return{kind:"error",stopReason:"error",publicMessage:s&&s!==t?s:GENERIC_RUNTIME_ERROR_PUBLIC_MESSAGE,errorCode:i?"provider_error":"unknown_runtime_error",diagnosticMessage:t,source:e.source,runtimeKind:r}}export function isRuntimeFailureInterrupted(e){return"interrupted"===e.kind}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{detectAgentRuntimeFailure}from"./agent-runtime-failure-classifier.js";export const SESSION_BUSY_PUBLIC_MESSAGE="The previous task is still
|
|
1
|
+
import{detectAgentRuntimeFailure}from"./agent-runtime-failure-classifier.js";export const SESSION_BUSY_PUBLIC_MESSAGE="The previous task is still running. Please wait for it to finish before sending another message.";export function interceptAgentRuntimeFailures(e,t,n){let i;const r=e=>{if("string"!=typeof e||!e.trim())return!1;const r=detectAgentRuntimeFailure({raw:e,source:t,runtimeKind:"openclaw"});return!!r&&(i||(i=r,n?.(r)),!0)};return{detectedFailure:()=>i,sink:{dispatcher:{...e.dispatcher,sendBlockReply:t=>!!r(t.text)||e.dispatcher.sendBlockReply(t),sendFinalReply:t=>!!r(t.text)||e.dispatcher.sendFinalReply(t)},replyOptions:{...e.replyOptions,onPartialReply:t=>{r(t.text)||e.replyOptions?.onPartialReply?.(t)}}}}}export function messageStopReasonForRuntimeFailure(e){return"timeout"===e.stopReason?"timeout":"error"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beeos-claw",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
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>",
|