beeos-claw 0.6.31 → 0.6.32

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{isSilentReplyPayloadText,startsWithSilentToken,stripHeartbeatToken,stripLeadingSilentToken,stripSilentToken}from"../auto-reply-tokens.js";const MESSAGE_TOOL_NAME="message";export function sanitizeEgressText(t){if(!t)return"";const e=stripHeartbeatToken(t,{mode:"message"});if(e.shouldSkip)return"";let n=e.text;return isSilentReplyPayloadText(n)?"":(startsWithSilentToken(n)&&(n=stripLeadingSilentToken(n)),n=stripSilentToken(n),n.trim())}export function classifyMediaRef(t){const e=t.trim();return e?/^https?:\/\//i.test(e)||/^beeos-file:\/\//i.test(e)?"remote":/^file:\/\//i.test(e)||e.startsWith("/")?"local":"unusable":"unusable"}export function localPathFromRef(t){const e=t.trim();return/^file:\/\//i.test(e)?decodeURI(e.slice(7)):e}function asNonEmptyString(t){return"string"==typeof t&&t.trim()?t.trim():void 0}export function extractMessageToolAttachments(t,e){if("message"!==t)return[];if(!e||"object"!=typeof e)return[];const n=e,r=[],s=(t,e,n)=>{t&&r.push({uri:t,...e?{name:e}:{},...n?{mimeType:n}:{}})},i=n.attachments;if(Array.isArray(i))for(const t of i){if(!t||"object"!=typeof t)continue;const e=t;s(asNonEmptyString(e.media)??asNonEmptyString(e.mediaUrl)??asNonEmptyString(e.url)??asNonEmptyString(e.fileUrl)??asNonEmptyString(e.path)??asNonEmptyString(e.filePath),asNonEmptyString(e.name),asNonEmptyString(e.mimeType))}return s(asNonEmptyString(n.path)??asNonEmptyString(n.filePath),void 0,asNonEmptyString(n.mimeType)),r}export function extractMessageToolCaption(t,e){if("message"!==t)return;if(!e||"object"!=typeof e)return;const n=e;return asNonEmptyString(n.message)??asNonEmptyString(n.caption)??asNonEmptyString(n.text)}export function buildBackgroundRunImEgress(t){if(!0!==t.backgroundRunAdopted)return{publish:!1,reason:"not_background_run"};if(t.cronJobId)return{publish:!1,reason:"cron_owned"};const e=t.channelId?.trim();if(!e)return{publish:!1,reason:"no_origin_conversation"};const n=t.currentRunId?.trim();if(!n)return{publish:!1,reason:"no_run_id"};const r=sanitizeEgressText(t.agentAssistantTextSoFar??t.chatAssistantTextSoFar)||sanitizeEgressText(t.backgroundRunCaption),s=dedupeArtifacts(t.backgroundRunArtifacts??[]);if(!r&&0===s.length)return{publish:!1,reason:"no_publishable_content"};const i=t.inboundMessageId?.trim();return{publish:!0,egress:{conversationId:e,runId:n,text:r,artifacts:s,...i?{replyTo:i}:{}}}}function dedupeArtifacts(t){const e=new Set,n=[];for(const r of t){const t=r?.uri?.trim();t&&!e.has(t)&&("unusable"!==classifyMediaRef(t)&&(e.add(t),n.push(r)))}return n}export async function resolveEgressMediaUrls(t,e){const n=[],r=[],s=new Set,i=t=>{s.has(t)||(s.add(t),n.push(t))};for(const n of t){const t=n?.uri?.trim();if(t)switch(classifyMediaRef(t)){case"remote":i(t);break;case"local":{if(!e){r.push(t);break}const s=await e(localPathFromRef(t),n.name);s?i(s):r.push(t);break}default:r.push(t)}}return{mediaUrls:n,unresolved:r}}export function backgroundRunEgressIdempotencyKey(t){return`bgrun:${t}`}export function backgroundRunEgressContent(t){return{...t.text?{text:t.text}:{},...t.mediaUrls.length>0?{media_urls:[...t.mediaUrls]}:{},background_run_id:t.runId}}
1
+ import{isSilentReplyPayloadText,startsWithSilentToken,stripHeartbeatToken,stripLeadingSilentToken,stripSilentToken}from"../auto-reply-tokens.js";const MESSAGE_TOOL_NAME="message";export function sanitizeEgressText(t){if(!t)return"";const e=stripHeartbeatToken(t,{mode:"message"});if(e.shouldSkip)return"";let n=e.text;return isSilentReplyPayloadText(n)?"":(startsWithSilentToken(n)&&(n=stripLeadingSilentToken(n)),n=stripSilentToken(n),n.trim())}export function classifyMediaRef(t){const e=t.trim();return e?/^https?:\/\//i.test(e)||/^beeos-file:\/\//i.test(e)?"remote":/^file:\/\//i.test(e)||e.startsWith("/")?"local":"unusable":"unusable"}export function localPathFromRef(t){const e=t.trim();return/^file:\/\//i.test(e)?decodeURI(e.slice(7)):e}function asNonEmptyString(t){return"string"==typeof t&&t.trim()?t.trim():void 0}export function extractMessageToolAttachments(t,e){if("message"!==t)return[];if(!e||"object"!=typeof e)return[];const n=e,r=[],s=(t,e,n)=>{t&&r.push({uri:t,...e?{name:e}:{},...n?{mimeType:n}:{}})},i=n.attachments;if(Array.isArray(i))for(const t of i){if(!t||"object"!=typeof t)continue;const e=t;s(asNonEmptyString(e.media)??asNonEmptyString(e.mediaUrl)??asNonEmptyString(e.url)??asNonEmptyString(e.fileUrl)??asNonEmptyString(e.path)??asNonEmptyString(e.filePath),asNonEmptyString(e.name),asNonEmptyString(e.mimeType))}return s(asNonEmptyString(n.path)??asNonEmptyString(n.filePath),void 0,asNonEmptyString(n.mimeType)),r}export function extractMessageToolCaption(t,e){if("message"!==t)return;if(!e||"object"!=typeof e)return;const n=e;return asNonEmptyString(n.message)??asNonEmptyString(n.caption)??asNonEmptyString(n.text)}export function buildBackgroundRunImEgress(t){if(!0!==t.backgroundRunAdopted)return{publish:!1,reason:"not_background_run"};if(t.cronJobId)return{publish:!1,reason:"cron_owned"};const e=t.channelId?.trim();if(!e)return{publish:!1,reason:"no_origin_conversation"};const n=t.currentRunId?.trim();if(!n)return{publish:!1,reason:"no_run_id"};const r=sanitizeEgressText(t.agentAssistantTextSoFar??t.chatAssistantTextSoFar)||sanitizeEgressText(t.backgroundRunCaption),s=dedupeArtifacts(t.backgroundRunArtifacts??[]);if(!r&&0===s.length)return{publish:!1,reason:"no_publishable_content"};const i=t.inboundMessageId?.trim();return{publish:!0,egress:{conversationId:e,runId:n,text:r,artifacts:s,...i?{replyTo:i}:{}}}}function dedupeArtifacts(t){const e=new Set,n=[];for(const r of t){const t=r?.uri?.trim();t&&!e.has(t)&&("unusable"!==classifyMediaRef(t)&&(e.add(t),n.push(r)))}return n}export async function resolveEgressMediaUrls(t,e){const n=[],r=[],s=new Set,i=t=>{s.has(t)||(s.add(t),n.push(t))};for(const n of t){const t=n?.uri?.trim();if(t)switch(classifyMediaRef(t)){case"remote":i(t);break;case"local":{if(!e){r.push(t);break}const s=await e(localPathFromRef(t),n.name);s?i(s):r.push(t);break}default:r.push(t)}}return{mediaUrls:n,unresolved:r}}export function backgroundRunEgressMessageId(t){return`bgrun_${t}`}export function backgroundRunEgressContent(t){return{...t.text?{text:t.text}:{},...t.mediaUrls.length>0?{media_urls:[...t.mediaUrls]}:{},background_run_id:t.runId}}
@@ -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{backgroundRunEgressContent,backgroundRunEgressIdempotencyKey,buildBackgroundRunImEgress,extractMessageToolAttachments,extractMessageToolCaption,resolveEgressMediaUrls}from"./background-run-im-egress.js";import{getUploadDeps,uploadLocalFile}from"../upload/upload-file.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,n=s&&"object"==typeof s?s:void 0,o="string"==typeof s||void 0!==n||"string"==typeof e.message||"string"==typeof e.reason;if(t||o){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 n?.message?n.message:void 0)??("string"==typeof e.reason?e.reason:void 0)??(n?JSON.stringify(n):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 n=roleFromSessionUpdate(t);n&&(e.lastTurnIdByRole||(e.lastTurnIdByRole={}),e.lastTurnIdByRole[n]=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 n=asString(t.toolCallId)??asString(t.tool_call_id)??asString(t.callId)??asString(t.id);if(n){const t=this.state.toolCallIdMap.get(n);return t?(e.sessionId&&this.state.toolCallToSessionMap.set(t,e.sessionId),t):(this.state.toolCallIdMap.set(n,n),e.sessionId&&this.state.toolCallToSessionMap.set(n,e.sessionId),n)}const o=`tc_${e.currentRunId??"unknown"}_${this.state.nextToolCallSeq++}`;return e.sessionId&&this.state.toolCallToSessionMap.set(o,e.sessionId),o}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 n of s.content){const o=n;if(!isRecord(o))continue;const a=asString(o.type);if(!a)continue;const r=toISO(s.timestamp);if("thinking"===a){if(t.hasAgentThinkingStream)continue;const e=normalizeText(o.thinking)??normalizeText(o.text);if(!e)continue;const s=dedupKey("chat:thinking",e);if(t.chatReplayDedupKeys.has(s))continue;t.chatReplayDedupKeys.add(s);const n={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:e},...r?{timestamp:r}:{}};this.sendTurnUpdate(t,n)&&t.assistantMq?.push(n);continue}if("toolCall"===a){if(this.sendToolCallSpacer(t,r),t.hasAgentToolStartStream)continue;const e=this.resolveToolCallId(t,o,"start"),s=asString(o.name)??asString(o.toolName)??asString(o.tool_name)??"tool",n=resolveToolArgs(o),a=dedupKey("chat:tool_start",e,JSON.stringify(n));if(t.chatReplayDedupKeys.has(a))continue;t.chatReplayDedupKeys.add(a);const i=buildToolCallPayload({toolCallId:e,toolName:s,args:n,timestamp:r});this.sendTurnUpdate(t,i)&&t.assistantMq?.push(i);continue}if("toolResult"===a){if(this.sendToolCallSpacer(t,r),t.hasAgentToolResultStream)continue;const e=this.resolveToolCallId(t,o,"result"),s=asString(o.name)??asString(o.toolName)??asString(o.tool_name)??"tool",n=o.result,a=normalizeText(o.text)??normalizeText(o.result)??(isRecord(n)||Array.isArray(n)?JSON.stringify(n,null,2):void 0);if(!a)continue;const i=dedupKey("chat:tool_result",e,a);if(t.chatReplayDedupKeys.has(i))continue;t.chatReplayDedupKeys.add(i),this.emitNormalizedToolResult(t,{toolCallId:e,toolName:s,rawText:a,rawResult:n,timestamp:r}).catch(e=>{this.logger.warn?.(`[gateway-events] Tool result normalization failed: ${e instanceof Error?e.message:e}`)});continue}if("text"===a&&o.text){if(t.hasAgentAssistantStream||t.agentAssistantTextSoFar)continue;const s=stripTransportMetadata(o.text);if(!s)continue;const n=classifyHeartbeatFilter(s);if(n.filtered){this.logHeartbeatSkip({source:"chat-text",reason:n.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 i=t.chatAssistantTextSoFar??"";let l;if(t.chatAssistantTextSoFar=s,s.startsWith(i))l=s.slice(i.length);else{if(i.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=${i.length}`);const d={sessionUpdate:"agent_message_chunk",content:{type:"text",text:l},...r?{timestamp:r}:{}};this.sendTurnUpdate(t,d),t.assistantMq?.push(d)}}}}else"delta"===e.state&&this.touchStreamActivity(t);if(this.maybeCompletePromptFromChatState(t,e),!t.promptDone)if("final"===e.state)this.maybePublishBackgroundRunToIm(t),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,n=classifyAgentRuntimeFailure({raw:s,source:"chat_state",runtimeKind:"openclaw"});"interrupted"===n.kind?this.observeLifecycle(this.interruptPrompt(t,n),"chat_interrupt",t):this.observeLifecycle(this.failPrompt(t,Errors.AgentError.rpcCode,n.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),n=e=>{this.sendTurnUpdate(t,e),t.assistantMq?.push(e)};if("assistant"===e.stream){t.hasAgentAssistantStream=!0;const o=this.state.extractAgentAssistantDelta(t,e.data??{});if(o){const a=normalizeTransportText(o);if(a){const o=t.agentAssistantTextSoFar??a,r=classifyHeartbeatFilter(o);r.filtered?this.logHeartbeatSkip({source:"assistant-delta",reason:r.reason,sessionId:t.sessionId,runId:e.runId,cumulative:o,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"}`),n({sessionUpdate:"agent_message_chunk",content:{type:"text",text:a},...s?{timestamp:s}:{}}))}}const a=[],r=e.data;r&&(Array.isArray(r.content)?a.push(...r.content):isRecord(r.content)?a.push(r.content):"string"==typeof r.type&&a.push(r));for(const e of a){if(!isRecord(e))continue;const o=asString(e.type);if("thinking"===o){t.hasAgentThinkingStream=!0;const n=normalizeText(e.thinking)??normalizeText(e.text);if(n){const e={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:n},...s?{timestamp:s}:{}};this.sendTurnUpdate(t,e)&&t.assistantMq?.push(e)}continue}if("toolCall"===o){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const o=this.resolveToolCallId(t,e,"start"),a=asString(e.name)??asString(e.toolName)??asString(e.tool_name)??"tool",r=resolveToolArgs(e);n(buildToolCallPayload({toolCallId:o,toolName:a,args:r,timestamp:s}));continue}if("toolResult"===o){this.sendToolCallSpacer(t,s),t.hasAgentToolResultStream=!0;const o=this.resolveToolCallId(t,e,"result"),a=asString(e.name)??asString(e.toolName)??asString(e.tool_name)??"tool",r=e.result,i=normalizeText(e.text)??normalizeText(e.result)??(isRecord(r)||Array.isArray(r)?JSON.stringify(r,null,2):void 0);i&&this.emitNormalizedToolResult(t,{toolCallId:o,toolName:a,rawText:i,rawResult:r,timestamp:s,pushUpdate:n}).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 n=normalizeText(e.data?.delta)??normalizeText(e.data?.text);if(n){const e={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:n},...s?{timestamp:s}:{}};this.sendTurnUpdate(t,e)&&t.assistantMq?.push(e)}}else if("tool"===e.stream){const o=e.data,a="string"==typeof o?.phase?o.phase:void 0;if("start"===a){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const e=this.resolveToolCallId(t,o,"start"),a=asString(o?.name)??asString(o?.toolName)??asString(o?.tool_name)??"tool",r=resolveToolArgs(o);this.captureBackgroundRunAttachments(t,a,r),n(buildToolCallPayload({toolCallId:e,toolName:a,args:r,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=o?.toolCall;if(e){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const a=asString(e?.name)??asString(o?.name)??asString(o?.toolName)??"tool",r={...o,...e},i=this.resolveToolCallId(t,r,"start"),l=resolveToolArgs(r);this.captureBackgroundRunAttachments(t,a,l),n(buildToolCallPayload({toolCallId:i,toolName:a,args:l,timestamp:s}))}}}else if("lifecycle"===e.stream){const s=e.data,n="string"==typeof s?.phase?s.phase:void 0,o=(n,o)=>{this.logger.warn?.(`[gateway-events] lifecycle error runId=${e.runId} sessionKey=${e.sessionKey} message=${n} data=${JSON.stringify(s)}`);const a=classifyAgentRuntimeFailure({raw:n,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),o,t)};if("end"===n){const e=lifecycleEndFailureMessage(s);if(e)o(e,"agent_failure");else{const e="string"==typeof s?.stopReason?s.stopReason:void 0;this.maybePublishBackgroundRunToIm(t),this.observeLifecycle(this.completePrompt(t,e||"end_turn"),"agent_final",t)}}else if("cancelled"===n||"cancel"===n)this.observeLifecycle(this.completePrompt(t,"cancelled"),"agent_cancelled",t);else if("error"===n){const e=s?.error,t=e&&"object"==typeof e?e:void 0;o(("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 n=t.data,o=n?.toolCall,a=("string"==typeof o?.id?o.id:void 0)??("string"==typeof n?.toolCallId?n.toolCallId:void 0),r=(a?this.state.toolCallIdMap.get(a):void 0)??a??`tc_${t.runId}_${t.seq}`,i=("string"==typeof o?.name?o.name:void 0)??("string"==typeof n?.name?n.name:void 0)??"tool",l="string"==typeof n?.text?n.text:void 0,d=a??r,c=n?.result;await this.emitNormalizedToolResult(e,{toolCallId:r,normalizeToolCallId:d,toolName:i,rawText:l,rawResult:c,timestamp:s})}async emitNormalizedToolResult(e,t){const s=t.pushUpdate??(t=>{this.sendTurnUpdate(e,t)&&e.assistantMq?.push(t)}),n=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)});!0===e.backgroundRunAdopted&&n.resourceLinks?.length&&(e.backgroundRunArtifacts??=[]).push(...n.resourceLinks)}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.backgroundRunAdopted=!0,e.backgroundRunArtifacts=[],e.assistantMq&&!e.assistantMq.closed||(e.assistantMq=new MessageQueue),this.schedulePromptTimeout(e),!0}maybePublishBackgroundRunToIm(e){const t=buildBackgroundRunImEgress(e);t.publish?(e.backgroundRunAdopted=!1,e.backgroundRunArtifacts=void 0,e.backgroundRunCaption=void 0,this.observeLifecycle(this.publishBackgroundRunResult(t.egress),"background_run_im_egress",e)):"not_background_run"!==t.reason&&"cron_owned"!==t.reason&&this.logger.debug?.(`[gateway-events] background run not published to IM (reason=${t.reason} sessionId=${e.sessionId} runId=${e.currentRunId??"n/a"})`)}captureBackgroundRunAttachments(e,t,s){if(!0!==e.backgroundRunAdopted)return;const n=extractMessageToolAttachments(t,s);n.length>0&&(e.backgroundRunArtifacts??=[]).push(...n);const o=extractMessageToolCaption(t,s);o&&(e.backgroundRunCaption=o)}async uploadEgressArtifact(e,t){const s=getUploadDeps();if(s)try{return(await uploadLocalFile(e,s.gatewayUrl,s.keys,{...t?{fileNameOverride:t}:{}})).uri}catch(t){return void this.logger.warn?.(`[gateway-events] background run artifact upload failed path=${e}: ${t instanceof Error?t.message:String(t)}`)}else this.logger.warn?.("[gateway-events] cannot upload background run artifact: upload deps unset")}async publishBackgroundRunResult(e){const t=this.getMessageClient?.();if(!t)return;const{mediaUrls:s,unresolved:n}=await resolveEgressMediaUrls(e.artifacts,(e,t)=>this.uploadEgressArtifact(e,t));if(n.length>0&&this.logger.warn?.(`[gateway-events] background run has ${n.length} unresolved artifact(s) run=${e.runId}: ${n.join(", ")}`),!e.text&&0===s.length)return void this.writeObs({component:"gateway-events",domain:"lifecycle",name:"background_run_im_egress.unresolved",severity:"warn",summary:`no publishable artifact for run ${e.runId}`,payload:{conversationId:e.conversationId,runId:e.runId,unresolvedCount:n.length}});const o=backgroundRunEgressIdempotencyKey(e.runId);try{await t.messages.send({conversationId:e.conversationId,clientMessageId:randomUUID(),idempotencyKey:o,type:"agent_reply",content:backgroundRunEgressContent({runId:e.runId,text:e.text,mediaUrls:s}),...e.replyTo?{replyTo:e.replyTo}:{}}),this.logger.info?.(`[gateway-events] published background run to conversation=${e.conversationId} run=${e.runId} mediaUrls=${s.length} textLen=${e.text.length}`),this.writeObs({component:"gateway-events",domain:"lifecycle",name:"background_run_im_egress.published",severity:"info",summary:`background run published to ${e.conversationId}`,payload:{conversationId:e.conversationId,runId:e.runId,mediaUrlCount:s.length,textLen:e.text.length,hasReplyTo:Boolean(e.replyTo)}})}catch(t){const s=t instanceof Error?t.message:String(t);if(isChannelGoneError(t,s))return void this.logger.info?.(`[gateway-events] origin conversation gone, skipping background run publish: conversation=${e.conversationId} run=${e.runId} err=${s}`);this.logger.warn?.(`[gateway-events] publishBackgroundRunResult failed: ${s}`),this.writeObs({component:"gateway-events",domain:"lifecycle",name:"background_run_im_egress.failed",severity:"warn",summary:s,payload:{conversationId:e.conversationId,runId:e.runId}})}}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 n=s?this.state.findSessionByKey(s)?.sessionId:void 0;if(n&&(t.sessionId=n),("string"!=typeof t.name||!t.name)&&"string"==typeof e.jobId&&e.jobId&&this.gatewayRequest)try{const s=await this.gatewayRequest("cron.list",{}),n=s?.jobs;if(Array.isArray(n))for(const s of n)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 o=`${this.config.agentGatewayUrl}/api/v1/agent/events`;try{await this.agentFetchFn(o,{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",{}),n=e?.jobs;if(Array.isArray(n))for(const e of n){if(!e||"object"!=typeof e)continue;const n=e;if(n.id===t){s=n;break}}}catch(e){return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel cron.list failed: ${e instanceof Error?e.message:e}`)}if(!s)return;const n="string"==typeof s.description?s.description:"";if(BEEOS_CHANNEL_TAG_RE.test(n)){const e=decodeBeeosChannelId(n);return void(e&&this.setCronChannelCacheLocal(t,e))}if("main"===("string"==typeof s.sessionTarget?s.sessionTarget:void 0))return;const o=isRecord(s.payload)?s.payload:void 0;if("systemEvent"===(o&&"string"==typeof o.kind?o.kind:void 0))return;const a=isRecord(s.delivery)?s.delivery:void 0;if(a&&"none"===a.mode)return;const r="string"==typeof s.sessionKey?s.sessionKey:void 0;if(!r)return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel: job has no sessionKey job=${t}`);const i=this.state.findSessionByKey(r),l=i?.channelId;if(!l)return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel: no channelId on ACPSession for sessionKey=${r} job=${t}`);const d=encodeBeeosChannelTag(n,l);try{await this.gatewayRequest("cron.update",{id:t,patch:{description:d}}),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 n="string"==typeof e.jobId?e.jobId:void 0;if(!n)return;const o=e.summary;let a=this.cronChannelCache?.get(n);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===n){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 r=randomUUID();try{await t.messages.send({conversationId:a,clientMessageId:r,idempotencyKey:r,type:"agent_reply",content:{text:o,cron_job_id:n,..."string"==typeof e.sessionKey?{session_key:e.sessionKey}:{}}}),this.logger.info?.(`[gateway-events] published cron result to channel=${a} job=${n}`)}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=${n} err=${t}`),void this.cronChannelCache?.delete(n);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{backgroundRunEgressContent,backgroundRunEgressMessageId,buildBackgroundRunImEgress,extractMessageToolAttachments,extractMessageToolCaption,resolveEgressMediaUrls}from"./background-run-im-egress.js";import{getUploadDeps,uploadLocalFile}from"../upload/upload-file.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,n=s&&"object"==typeof s?s:void 0,o="string"==typeof s||void 0!==n||"string"==typeof e.message||"string"==typeof e.reason;if(t||o){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 n?.message?n.message:void 0)??("string"==typeof e.reason?e.reason:void 0)??(n?JSON.stringify(n):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 n=roleFromSessionUpdate(t);n&&(e.lastTurnIdByRole||(e.lastTurnIdByRole={}),e.lastTurnIdByRole[n]=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 n=asString(t.toolCallId)??asString(t.tool_call_id)??asString(t.callId)??asString(t.id);if(n){const t=this.state.toolCallIdMap.get(n);return t?(e.sessionId&&this.state.toolCallToSessionMap.set(t,e.sessionId),t):(this.state.toolCallIdMap.set(n,n),e.sessionId&&this.state.toolCallToSessionMap.set(n,e.sessionId),n)}const o=`tc_${e.currentRunId??"unknown"}_${this.state.nextToolCallSeq++}`;return e.sessionId&&this.state.toolCallToSessionMap.set(o,e.sessionId),o}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 n of s.content){const o=n;if(!isRecord(o))continue;const a=asString(o.type);if(!a)continue;const r=toISO(s.timestamp);if("thinking"===a){if(t.hasAgentThinkingStream)continue;const e=normalizeText(o.thinking)??normalizeText(o.text);if(!e)continue;const s=dedupKey("chat:thinking",e);if(t.chatReplayDedupKeys.has(s))continue;t.chatReplayDedupKeys.add(s);const n={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:e},...r?{timestamp:r}:{}};this.sendTurnUpdate(t,n)&&t.assistantMq?.push(n);continue}if("toolCall"===a){if(this.sendToolCallSpacer(t,r),t.hasAgentToolStartStream)continue;const e=this.resolveToolCallId(t,o,"start"),s=asString(o.name)??asString(o.toolName)??asString(o.tool_name)??"tool",n=resolveToolArgs(o),a=dedupKey("chat:tool_start",e,JSON.stringify(n));if(t.chatReplayDedupKeys.has(a))continue;t.chatReplayDedupKeys.add(a);const i=buildToolCallPayload({toolCallId:e,toolName:s,args:n,timestamp:r});this.sendTurnUpdate(t,i)&&t.assistantMq?.push(i);continue}if("toolResult"===a){if(this.sendToolCallSpacer(t,r),t.hasAgentToolResultStream)continue;const e=this.resolveToolCallId(t,o,"result"),s=asString(o.name)??asString(o.toolName)??asString(o.tool_name)??"tool",n=o.result,a=normalizeText(o.text)??normalizeText(o.result)??(isRecord(n)||Array.isArray(n)?JSON.stringify(n,null,2):void 0);if(!a)continue;const i=dedupKey("chat:tool_result",e,a);if(t.chatReplayDedupKeys.has(i))continue;t.chatReplayDedupKeys.add(i),this.emitNormalizedToolResult(t,{toolCallId:e,toolName:s,rawText:a,rawResult:n,timestamp:r}).catch(e=>{this.logger.warn?.(`[gateway-events] Tool result normalization failed: ${e instanceof Error?e.message:e}`)});continue}if("text"===a&&o.text){if(t.hasAgentAssistantStream||t.agentAssistantTextSoFar)continue;const s=stripTransportMetadata(o.text);if(!s)continue;const n=classifyHeartbeatFilter(s);if(n.filtered){this.logHeartbeatSkip({source:"chat-text",reason:n.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 i=t.chatAssistantTextSoFar??"";let l;if(t.chatAssistantTextSoFar=s,s.startsWith(i))l=s.slice(i.length);else{if(i.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=${i.length}`);const d={sessionUpdate:"agent_message_chunk",content:{type:"text",text:l},...r?{timestamp:r}:{}};this.sendTurnUpdate(t,d),t.assistantMq?.push(d)}}}}else"delta"===e.state&&this.touchStreamActivity(t);if(this.maybeCompletePromptFromChatState(t,e),!t.promptDone)if("final"===e.state)this.maybePublishBackgroundRunToIm(t),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,n=classifyAgentRuntimeFailure({raw:s,source:"chat_state",runtimeKind:"openclaw"});"interrupted"===n.kind?this.observeLifecycle(this.interruptPrompt(t,n),"chat_interrupt",t):this.observeLifecycle(this.failPrompt(t,Errors.AgentError.rpcCode,n.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),n=e=>{this.sendTurnUpdate(t,e),t.assistantMq?.push(e)};if("assistant"===e.stream){t.hasAgentAssistantStream=!0;const o=this.state.extractAgentAssistantDelta(t,e.data??{});if(o){const a=normalizeTransportText(o);if(a){const o=t.agentAssistantTextSoFar??a,r=classifyHeartbeatFilter(o);r.filtered?this.logHeartbeatSkip({source:"assistant-delta",reason:r.reason,sessionId:t.sessionId,runId:e.runId,cumulative:o,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"}`),n({sessionUpdate:"agent_message_chunk",content:{type:"text",text:a},...s?{timestamp:s}:{}}))}}const a=[],r=e.data;r&&(Array.isArray(r.content)?a.push(...r.content):isRecord(r.content)?a.push(r.content):"string"==typeof r.type&&a.push(r));for(const e of a){if(!isRecord(e))continue;const o=asString(e.type);if("thinking"===o){t.hasAgentThinkingStream=!0;const n=normalizeText(e.thinking)??normalizeText(e.text);if(n){const e={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:n},...s?{timestamp:s}:{}};this.sendTurnUpdate(t,e)&&t.assistantMq?.push(e)}continue}if("toolCall"===o){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const o=this.resolveToolCallId(t,e,"start"),a=asString(e.name)??asString(e.toolName)??asString(e.tool_name)??"tool",r=resolveToolArgs(e);n(buildToolCallPayload({toolCallId:o,toolName:a,args:r,timestamp:s}));continue}if("toolResult"===o){this.sendToolCallSpacer(t,s),t.hasAgentToolResultStream=!0;const o=this.resolveToolCallId(t,e,"result"),a=asString(e.name)??asString(e.toolName)??asString(e.tool_name)??"tool",r=e.result,i=normalizeText(e.text)??normalizeText(e.result)??(isRecord(r)||Array.isArray(r)?JSON.stringify(r,null,2):void 0);i&&this.emitNormalizedToolResult(t,{toolCallId:o,toolName:a,rawText:i,rawResult:r,timestamp:s,pushUpdate:n}).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 n=normalizeText(e.data?.delta)??normalizeText(e.data?.text);if(n){const e={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:n},...s?{timestamp:s}:{}};this.sendTurnUpdate(t,e)&&t.assistantMq?.push(e)}}else if("tool"===e.stream){const o=e.data,a="string"==typeof o?.phase?o.phase:void 0;if("start"===a){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const e=this.resolveToolCallId(t,o,"start"),a=asString(o?.name)??asString(o?.toolName)??asString(o?.tool_name)??"tool",r=resolveToolArgs(o);this.captureBackgroundRunAttachments(t,a,r),n(buildToolCallPayload({toolCallId:e,toolName:a,args:r,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=o?.toolCall;if(e){this.sendToolCallSpacer(t,s),t.hasAgentToolStartStream=!0;const a=asString(e?.name)??asString(o?.name)??asString(o?.toolName)??"tool",r={...o,...e},i=this.resolveToolCallId(t,r,"start"),l=resolveToolArgs(r);this.captureBackgroundRunAttachments(t,a,l),n(buildToolCallPayload({toolCallId:i,toolName:a,args:l,timestamp:s}))}}}else if("lifecycle"===e.stream){const s=e.data,n="string"==typeof s?.phase?s.phase:void 0,o=(n,o)=>{this.logger.warn?.(`[gateway-events] lifecycle error runId=${e.runId} sessionKey=${e.sessionKey} message=${n} data=${JSON.stringify(s)}`);const a=classifyAgentRuntimeFailure({raw:n,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),o,t)};if("end"===n){const e=lifecycleEndFailureMessage(s);if(e)o(e,"agent_failure");else{const e="string"==typeof s?.stopReason?s.stopReason:void 0;this.maybePublishBackgroundRunToIm(t),this.observeLifecycle(this.completePrompt(t,e||"end_turn"),"agent_final",t)}}else if("cancelled"===n||"cancel"===n)this.observeLifecycle(this.completePrompt(t,"cancelled"),"agent_cancelled",t);else if("error"===n){const e=s?.error,t=e&&"object"==typeof e?e:void 0;o(("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 n=t.data,o=n?.toolCall,a=("string"==typeof o?.id?o.id:void 0)??("string"==typeof n?.toolCallId?n.toolCallId:void 0),r=(a?this.state.toolCallIdMap.get(a):void 0)??a??`tc_${t.runId}_${t.seq}`,i=("string"==typeof o?.name?o.name:void 0)??("string"==typeof n?.name?n.name:void 0)??"tool",l="string"==typeof n?.text?n.text:void 0,d=a??r,c=n?.result;await this.emitNormalizedToolResult(e,{toolCallId:r,normalizeToolCallId:d,toolName:i,rawText:l,rawResult:c,timestamp:s})}async emitNormalizedToolResult(e,t){const s=t.pushUpdate??(t=>{this.sendTurnUpdate(e,t)&&e.assistantMq?.push(t)}),n=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)});!0===e.backgroundRunAdopted&&n.resourceLinks?.length&&(e.backgroundRunArtifacts??=[]).push(...n.resourceLinks)}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.backgroundRunAdopted=!0,e.backgroundRunArtifacts=[],e.assistantMq&&!e.assistantMq.closed||(e.assistantMq=new MessageQueue),this.schedulePromptTimeout(e),!0}maybePublishBackgroundRunToIm(e){const t=buildBackgroundRunImEgress(e);t.publish?(e.backgroundRunAdopted=!1,e.backgroundRunArtifacts=void 0,e.backgroundRunCaption=void 0,this.observeLifecycle(this.publishBackgroundRunResult(t.egress),"background_run_im_egress",e)):"not_background_run"!==t.reason&&"cron_owned"!==t.reason&&this.logger.debug?.(`[gateway-events] background run not published to IM (reason=${t.reason} sessionId=${e.sessionId} runId=${e.currentRunId??"n/a"})`)}captureBackgroundRunAttachments(e,t,s){if(!0!==e.backgroundRunAdopted)return;const n=extractMessageToolAttachments(t,s);n.length>0&&(e.backgroundRunArtifacts??=[]).push(...n);const o=extractMessageToolCaption(t,s);o&&(e.backgroundRunCaption=o)}async uploadEgressArtifact(e,t){const s=getUploadDeps();if(s)try{return(await uploadLocalFile(e,s.gatewayUrl,s.keys,{...t?{fileNameOverride:t}:{}})).uri}catch(t){return void this.logger.warn?.(`[gateway-events] background run artifact upload failed path=${e}: ${t instanceof Error?t.message:String(t)}`)}else this.logger.warn?.("[gateway-events] cannot upload background run artifact: upload deps unset")}async publishBackgroundRunResult(e){const t=this.getMessageClient?.();if(!t)return;const{mediaUrls:s,unresolved:n}=await resolveEgressMediaUrls(e.artifacts,(e,t)=>this.uploadEgressArtifact(e,t));if(n.length>0&&this.logger.warn?.(`[gateway-events] background run has ${n.length} unresolved artifact(s) run=${e.runId}: ${n.join(", ")}`),!e.text&&0===s.length)return void this.writeObs({component:"gateway-events",domain:"lifecycle",name:"background_run_im_egress.unresolved",severity:"warn",summary:`no publishable artifact for run ${e.runId}`,payload:{conversationId:e.conversationId,runId:e.runId,unresolvedCount:n.length}});const o=backgroundRunEgressMessageId(e.runId);try{await t.messages.send({conversationId:e.conversationId,clientMessageId:o,idempotencyKey:o,type:"agent_reply",content:backgroundRunEgressContent({runId:e.runId,text:e.text,mediaUrls:s}),...e.replyTo?{replyTo:e.replyTo}:{}}),this.logger.info?.(`[gateway-events] published background run to conversation=${e.conversationId} run=${e.runId} mediaUrls=${s.length} textLen=${e.text.length}`),this.writeObs({component:"gateway-events",domain:"lifecycle",name:"background_run_im_egress.published",severity:"info",summary:`background run published to ${e.conversationId}`,payload:{conversationId:e.conversationId,runId:e.runId,mediaUrlCount:s.length,textLen:e.text.length,hasReplyTo:Boolean(e.replyTo)}})}catch(t){const s=t instanceof Error?t.message:String(t);if(isChannelGoneError(t,s))return void this.logger.info?.(`[gateway-events] origin conversation gone, skipping background run publish: conversation=${e.conversationId} run=${e.runId} err=${s}`);this.logger.warn?.(`[gateway-events] publishBackgroundRunResult failed: ${s}`),this.writeObs({component:"gateway-events",domain:"lifecycle",name:"background_run_im_egress.failed",severity:"warn",summary:s,payload:{conversationId:e.conversationId,runId:e.runId}})}}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 n=s?this.state.findSessionByKey(s)?.sessionId:void 0;if(n&&(t.sessionId=n),("string"!=typeof t.name||!t.name)&&"string"==typeof e.jobId&&e.jobId&&this.gatewayRequest)try{const s=await this.gatewayRequest("cron.list",{}),n=s?.jobs;if(Array.isArray(n))for(const s of n)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 o=`${this.config.agentGatewayUrl}/api/v1/agent/events`;try{await this.agentFetchFn(o,{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",{}),n=e?.jobs;if(Array.isArray(n))for(const e of n){if(!e||"object"!=typeof e)continue;const n=e;if(n.id===t){s=n;break}}}catch(e){return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel cron.list failed: ${e instanceof Error?e.message:e}`)}if(!s)return;const n="string"==typeof s.description?s.description:"";if(BEEOS_CHANNEL_TAG_RE.test(n)){const e=decodeBeeosChannelId(n);return void(e&&this.setCronChannelCacheLocal(t,e))}if("main"===("string"==typeof s.sessionTarget?s.sessionTarget:void 0))return;const o=isRecord(s.payload)?s.payload:void 0;if("systemEvent"===(o&&"string"==typeof o.kind?o.kind:void 0))return;const a=isRecord(s.delivery)?s.delivery:void 0;if(a&&"none"===a.mode)return;const r="string"==typeof s.sessionKey?s.sessionKey:void 0;if(!r)return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel: job has no sessionKey job=${t}`);const i=this.state.findSessionByKey(r),l=i?.channelId;if(!l)return void this.logger.debug?.(`[gateway-events] maybeBackfillBeeosChannel: no channelId on ACPSession for sessionKey=${r} job=${t}`);const d=encodeBeeosChannelTag(n,l);try{await this.gatewayRequest("cron.update",{id:t,patch:{description:d}}),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 n="string"==typeof e.jobId?e.jobId:void 0;if(!n)return;const o=e.summary;let a=this.cronChannelCache?.get(n);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===n){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 r=randomUUID();try{await t.messages.send({conversationId:a,clientMessageId:r,idempotencyKey:r,type:"agent_reply",content:{text:o,cron_job_id:n,..."string"==typeof e.sessionKey?{session_key:e.sessionKey}:{}}}),this.logger.info?.(`[gateway-events] published cron result to channel=${a} job=${n}`)}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=${n} err=${t}`),void this.cronChannelCache?.delete(n);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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.6.31",
3
+ "version": "0.6.32",
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>",