beeos-claw 0.6.54-staging.4 → 0.6.54

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{dispatchRuntimeReplyWithRoute,hasRuntimeReplyApi}from"./runtime/reply-router.js";import{createImReplySink}from"./runtime/im-reply-sink.js";import{SerializedCallbackQueue}from"./runtime/callback-queue.js";import{promptRunKey}from"./runtime/prompt-run-store.js";import{classifyAgentRuntimeFailure}from"./runtime/agent-runtime-failure-classifier.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"./runtime/session-mutation-coordinator.js";import{interceptAgentRuntimeFailures,messageStopReasonForRuntimeFailure,SESSION_BUSY_PUBLIC_MESSAGE}from"./runtime/im-runtime-failure-policy.js";import{maybeRenameImConversationTitle}from"./runtime/conversation-title.js";import{logChatLatency}from"./chat-latency.js";const normalizePromptTimeoutMs=e=>{if("number"!=typeof e||!Number.isFinite(e))return;const n=Math.max(0,Math.trunc(e));return n>0?n:void 0},ABORT_TERMINAL_GRACE_MS=1e4,formatErrMessage=e=>{if(e instanceof Error)return e.message;if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}},failMessageStream=async(e,n)=>{void 0!==n.body&&e.setBody(n.body),await e.fail(void 0===n.stopReason?void 0:{stopReason:n.stopReason})};export const deterministicReplyMessageId=(e,n,t,a)=>`${e}_${createHash("sha256").update(`${n}:${t}:${a}`).digest("hex").slice(0,32)}`;export const dispatchImInboundPrompt=async e=>{const{runtime:n,prompt:t,client:a,logger:r,promptRunStore:i,assistantMessageCommitRouter:s,toolResultObservationRouter:o,sessionMutationCoordinator:c,abortTransport:d,activeRunAuthority:l}=e;if(!hasRuntimeReplyApi(n))return r.warn?.(`[im] runtime dispatch skipped reason=reply_api_unavailable channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_api_unavailable",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),!1;const u=t.inboundMessageId,m=randomUUID(),p=Date.now();logChatLatency(r,"claw.inbound.received",u,p,{origin:e.origin??"im"});const I=e.origin??"im",g={protocol:"im",requestId:u,sessionId:t.sessionKey??"agent:main",sourceMessageId:t.inboundMessageId,chatId:t.channelId,origin:I},h=normalizePromptTimeoutMs(e.promptTimeoutMs),y=async(n,a)=>{if(e.onTerminalResult)try{await e.onTerminalResult(n,a)}catch(e){r.warn?.(`[im] terminal business callback failed requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}},_=promptRunKey(t.channelId,t.inboundMessageId),f=t.sessionKey??"agent:main",w=new AbortController;let R,b,E;const M=new Promise((e,n)=>{E=n});let $;const S=()=>{void 0!==$&&(clearTimeout($),$=void 0)},v=async e=>{if(b??=e,w.abort(e),!R)return{ok:!0};const n=await c.requestCancel(R,d);return n.ok?n.terminalObservation.then(e=>{e.ok||E(new Error(SESSION_RECOVERY_REQUIRED_CODE))}):E(new Error(SESSION_RECOVERY_REQUIRED_CODE)),{ok:n.ok}};if(!i.register(_,{requestId:u,channelId:t.channelId,requestCancel:v,startedAt:p,sessionKey:f}))return r.warn?.(`[im] runtime dispatch rejected duplicate inboundMessageId=${t.inboundMessageId} requestId=${u} channel=${t.channelId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"duplicate_inbound_message_id",cause:"abort"}),!1;const C=deterministicReplyMessageId("asst",t.channelId,t.inboundMessageId,t.selfPrincipalId),q=a.messages.startStream({conversationId:t.channelId,clientMessageId:C,idempotencyKey:C,replyTo:e.inReplyTo??t.inboundMessageId,type:"agent_reply",content:{}});let k,O=!1;try{const e=await q.opened();O="duplicate"===e.outcome,logChatLatency(r,"claw.reply_stream.opened",u,p,{outcome:e.outcome})}catch(n){let s="unconfirmed";try{s=(await a.messages.getEnvelope(t.channelId,C)).state}catch{}return r.warn?.(`[im] runtime reply stream open outcome unknown requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId} reply_id=${C} reconciled_state=${s}: ${formatErrMessage(n)}; runtime_not_invoked`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_open_outcome_unknown",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(_),!1}if(O)return r.info?.(`[im] runtime dispatch skipped existing deterministic reply requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"idempotent_reply_exists",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(_),!1;const T=new SerializedCallbackQueue,A=createImReplySink({stream:q,openReasoningStream:()=>{const n=deterministicReplyMessageId("rsn",t.channelId,t.inboundMessageId,t.selfPrincipalId);return k=a.messages.startStream({conversationId:t.channelId,clientMessageId:n,idempotencyKey:n,replyTo:e.inReplyTo??t.inboundMessageId,type:"agent_reasoning",content:{}}),k},abortSignal:w.signal,callbackQueue:T,logger:r,requestId:u,startedAtMs:p,runId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,origin:I,onAgentRunStart:e=>{c.bindRun(R,e)}}),L=interceptAgentRuntimeFailures(A,"im_runtime",e=>r.warn?.(`[im] runtime failure reply suppressed requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId} error_code=${e.errorCode}`)),D=L.sink,U=async(e,n)=>{if(!k)return!1;try{return"cancel"===e?await k.cancel({stopReason:"user_stop"}):"fail"===e?await k.fail(n?{stopReason:n}:void 0):await k.finalize({stopReason:"end_turn"}),!1}catch(e){return r.warn?.(`[im] runtime reasoning stream terminal write failed requestId=${u} channel=${t.channelId}: ${formatErrMessage(e)}`),!0}};let K,N,F=!1;const Q=async e=>{D.dispatcher.markComplete(e);try{await D.dispatcher.waitForIdle()}catch{}},P=()=>{S();const n=normalizePromptTimeoutMs(e.abortTerminalGraceMs)??1e4;$=setTimeout(()=>{$=void 0,(async()=>{if(q.isTerminated)return;const e=w.signal.reason??b,n=void 0!==N,a=!n&&"string"==typeof e&&e.startsWith("chat_cancel");if(await Q("failure"),!q.isTerminated)try{a?(await U("cancel"),await q.cancel({stopReason:"user_stop"})):n?(await U("fail","timeout"),await failMessageStream(q,{body:N?.message??"IM runtime dispatch timed out",stopReason:"timeout"})):(await U("fail"),await failMessageStream(q,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"})),r.warn?.(`[im] abort settlement watchdog terminalized reply requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId} user_cancel=${a} timeout=${n}`)}catch(e){r.warn?.(`[im] abort settlement watchdog terminal write failed requestId=${u} channel=${t.channelId}: ${formatErrMessage(e)}`)}})()},n)};w.signal.aborted?P():w.signal.addEventListener("abort",P,{once:!0});const Y=()=>{if(!w.signal.aborted)return;const e=w.signal.reason;throw e instanceof Error?e:new Error(String(e??"runtime dispatch aborted"))};let j,G,z,V,B;const x=async e=>{if(!R)throw new Error("session_mutation_not_acquired");const n=await c.converge(R,e);return n.ok?(j=n.permit,!0):(r.error?.(`[im] session mutation recovery required requestId=${u} sessionKey=${f} reason=${n.recovery.reason}`),G={code:n.recovery.code,reason:n.recovery.reason,sessionKey:n.recovery.sessionKey,operationId:n.recovery.operationId,surface:"message_service",...n.recovery.terminalKind?{terminalKind:n.recovery.terminalKind}:{}},!1)};try{Y();const d=c.tryStartInteractive({sessionKey:f,label:"im_chat_message"},"message_service");if(!d.ok){try{await failMessageStream(q,{body:SESSION_BUSY_PUBLIC_MESSAGE,stopReason:"error"})}catch(e){r.warn?.(`[im] session busy terminal write failed channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return r.warn?.(`[im] session write gate busy sessionKey=${f} channel=${t.channelId} in_reply_to=${t.inboundMessageId} active_operation=${d.activeOperationId??"unknown"}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"session_busy",cause:"runtime_error"}),!1}if(R=d.token,logChatLatency(r,"claw.session_gate.acquired",u,p),c.bindRun(R,m),V=s.register(f,A),B=o.register(f,A),l){const e=c.snapshot(f);z={runtimeConversationUid:t.channelId,runtimeSessionKey:t.channelId,historyGeneration:l.historyGeneration(t.channelId),targetMessageId:t.inboundMessageId,runtimeRunId:m,activeRunRegistrationId:`im:${e?.operationId??m}`},l.activeRuns.register(z,async()=>{const e=`chat_cancel:activeRun:${z.activeRunRegistrationId}`,n=await i.abort(_,e);return n.ok||"already_aborted"===n.reason?"accepted":"terminal_committing"===n.reason||"not_found"===n.reason?"already_terminal":"recovery_required"})}Y(),r.info?.(`[im] runtime dispatching requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId} origin=${I}`),e.onLifecycleEvent?.({type:"dispatch_start",requestId:u,channelId:t.channelId,inboundMessageId:t.inboundMessageId}),logChatLatency(r,"claw.runtime.invoke",u,p);const w=dispatchRuntimeReplyWithRoute({runtime:n,pluginConfig:e.pluginConfig,preparedPrompt:t.dispatchInput,replyRoute:g,replySinks:{im:D},onDispatchInvoked:()=>{F=!0}});void 0!==h&&(K=setTimeout(()=>{N=new Error(`IM runtime dispatch timed out after ${h}ms`),r.warn?.(`[im] runtime dispatch timeout requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId} timeout_ms=${h}`),v(N)},h));const b=Promise.race([w,M]),E=await b.finally(()=>{K&&(clearTimeout(K),K=void 0)}),$=L.detectedFailure();if($)throw new Error($.diagnosticMessage);if(Y(),await Q("success"),Y(),!i.claimTerminal(_,"completion")){if(Y(),"cancelling"===i.phase(_))throw new Error("cancellation in progress");throw new Error("terminal outcome already claimed")}if(!await x("completed"))return await U("fail"),await failMessageStream(q,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:"runtime_error",...G?{recovery:G}:{}}),!1;const S=D.dispatcher.getTerminalErrorText?.();if(void 0!==S){await U("fail");let n=!1;try{await failMessageStream(q,{body:S,stopReason:"error"})}catch(e){n=!0,r.warn?.(`[im] runtime reply stream error-terminal write outcome unknown requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return l&&z&&l.activeRuns.markTerminal(z,"failed"),r.warn?.(`[im] runtime dispatch ended with an error terminal requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(E.counts)} terminalWriteFailed=${n} elapsed_ms=${Date.now()-p}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"runtime_error_terminal_reply",cause:"runtime_error"}),n||await y("",S),!1}const C=await U("complete");let k=!1;try{await q.finalize({stopReason:"end_turn"})}catch(e){k=!0,r.warn?.(`[im] runtime reply stream finalize outcome unknown requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}l&&z&&l.activeRuns.markTerminal(z,"completed");const O={...E.counts,source:A.getSourceCount()};r.info?.(`[im] runtime dispatch completed requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(O)} elapsed_ms=${Date.now()-p}`),logChatLatency(r,"claw.turn.terminal",u,p,{outcome:k?"outcome_unknown":"completed",tool_count:O.tool}),e.onLifecycleEvent?.({type:"dispatch_complete",requestId:u,channelId:t.channelId,inboundMessageId:t.inboundMessageId,counts:O,...k?{terminalWriteFailed:!0}:{},...C?{reasoningTerminalWriteFailed:!0}:{}});const T=D.dispatcher.getAccumulatedBody?.()??"";k||await y(T,"");const P="string"==typeof t.dispatchInput.message?t.dispatchInput.message:"";return maybeRenameImConversationTitle({client:a,conversationId:t.channelId,question:P,answer:T,logger:r}),!0}catch(n){await Q("failure");const a=w.signal.aborted,s=a?w.signal.reason:void 0,o="cancelling"===i.phase(_),c=void 0!==N,d=s??b,m=!c&&"string"==typeof d&&d.startsWith("chat_cancel");let I="runtime_error";c?I="timeout":(a||o)&&(I="abort");const g=o||m||c||a?"cancellation":"completion";if(!i.claimTerminal(_,g)){if(r.warn?.(`[im] terminal claim raced requestId=${u} channel=${t.channelId}; best-effort envelope terminal if still streaming`),!q.isTerminated)try{m?(await U("cancel"),await q.cancel({stopReason:"user_stop"})):c?(await U("fail","timeout"),await failMessageStream(q,{body:N?.message??formatErrMessage(n),stopReason:"timeout"})):(await U("fail"),await failMessageStream(q,{body:formatErrMessage(n),stopReason:"error"}))}catch(e){r.warn?.(`[im] raced terminal write failed requestId=${u}: ${formatErrMessage(e)}`)}throw n}const h=m?"cancelled":c?"timeout":"error";if(l&&z&&l.activeRuns.markTerminal(z,"cancelled"===h?"cancelled":"failed"),R&&!await x(h))throw m?(await U("cancel"),await q.cancel({stopReason:"user_stop"}).catch(()=>{})):(await U("fail"),await failMessageStream(q,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{})),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:I,...G?{recovery:G}:{}}),n;const f=classifyAgentRuntimeFailure({raw:n,source:"im_runtime",runtimeKind:"openclaw",requestAborted:m}),E=m?f:L.detectedFailure()??f;m?await U("cancel"):c?await U("fail","timeout"):await U("fail");let M=!1;try{m?await q.cancel({stopReason:"user_stop"}):await failMessageStream(q,{body:E.publicMessage,stopReason:messageStopReasonForRuntimeFailure(E)})}catch(e){M=!0,r.warn?.(`[im] runtime stream terminal write failed requestId=${u} channel=${t.channelId}: ${formatErrMessage(e)}`)}throw r.warn?.(`[im] runtime dispatch failed requestId=${u} channel=${t.channelId} in_reply_to=${t.inboundMessageId} cause=${I} dispatchInvoked=${F} error_code=${E.errorCode}`),logChatLatency(r,"claw.turn.terminal",u,p,{outcome:I,error_code:E.errorCode}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:E.errorCode,cause:I}),m||M||await y("",E.publicMessage),n}finally{S(),B?.(),V?.(),i.unregister(_),F?j&&c.release(j):c.releaseBeforeRuntime(R)}};
1
+ import{createHash,randomUUID}from"node:crypto";import{dispatchRuntimeReplyWithRoute,hasRuntimeReplyApi}from"./runtime/reply-router.js";import{createImReplySink}from"./runtime/im-reply-sink.js";import{SerializedCallbackQueue}from"./runtime/callback-queue.js";import{promptRunKey}from"./runtime/prompt-run-store.js";import{classifyAgentRuntimeFailure}from"./runtime/agent-runtime-failure-classifier.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"./runtime/session-mutation-coordinator.js";import{interceptAgentRuntimeFailures,messageStopReasonForRuntimeFailure,SESSION_BUSY_PUBLIC_MESSAGE}from"./runtime/im-runtime-failure-policy.js";import{maybeRenameImConversationTitle}from"./runtime/conversation-title.js";const normalizePromptTimeoutMs=e=>{if("number"!=typeof e||!Number.isFinite(e))return;const n=Math.max(0,Math.trunc(e));return n>0?n:void 0},ABORT_TERMINAL_GRACE_MS=1e4,formatErrMessage=e=>{if(e instanceof Error)return e.message;if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}},failMessageStream=async(e,n)=>{void 0!==n.body&&e.setBody(n.body),await e.fail(void 0===n.stopReason?void 0:{stopReason:n.stopReason})};export const deterministicReplyMessageId=(e,n,a,t)=>`${e}_${createHash("sha256").update(`${n}:${a}:${t}`).digest("hex").slice(0,32)}`;export const dispatchImInboundPrompt=async e=>{const{runtime:n,prompt:a,client:t,logger:r,promptRunStore:i,assistantMessageCommitRouter:s,toolResultObservationRouter:o,sessionMutationCoordinator:c,abortTransport:d,activeRunAuthority:l}=e;if(!hasRuntimeReplyApi(n))return r.warn?.(`[im] runtime dispatch skipped reason=reply_api_unavailable channel=${a.channelId} in_reply_to=${a.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_api_unavailable",channelId:a.channelId,inboundMessageId:a.inboundMessageId}),!1;const u=`im_req_${randomUUID().replace(/-/g,"")}`,m=randomUUID(),p=Date.now(),I=e.origin??"im",g={protocol:"im",requestId:u,sessionId:a.sessionKey??"agent:main",sourceMessageId:a.inboundMessageId,chatId:a.channelId,origin:I},h=normalizePromptTimeoutMs(e.promptTimeoutMs),y=async(n,t)=>{if(e.onTerminalResult)try{await e.onTerminalResult(n,t)}catch(e){r.warn?.(`[im] terminal business callback failed requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}`)}},_=promptRunKey(a.channelId,a.inboundMessageId),f=a.sessionKey??"agent:main",R=new AbortController;let w,E,b;const M=new Promise((e,n)=>{b=n});let $;const S=()=>{void 0!==$&&(clearTimeout($),$=void 0)},v=async e=>{if(E??=e,R.abort(e),!w)return{ok:!0};const n=await c.requestCancel(w,d);return n.ok?n.terminalObservation.then(e=>{e.ok||b(new Error(SESSION_RECOVERY_REQUIRED_CODE))}):b(new Error(SESSION_RECOVERY_REQUIRED_CODE)),{ok:n.ok}};if(!i.register(_,{requestId:u,channelId:a.channelId,requestCancel:v,startedAt:p,sessionKey:f}))return r.warn?.(`[im] runtime dispatch rejected duplicate inboundMessageId=${a.inboundMessageId} requestId=${u} channel=${a.channelId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:"duplicate_inbound_message_id",cause:"abort"}),!1;const q=deterministicReplyMessageId("asst",a.channelId,a.inboundMessageId,a.selfPrincipalId),k=t.messages.startStream({conversationId:a.channelId,clientMessageId:q,idempotencyKey:q,replyTo:e.inReplyTo??a.inboundMessageId,type:"agent_reply",content:{}});let C,O=!1;try{O="duplicate"===(await k.opened()).outcome}catch(n){let s="unconfirmed";try{s=(await t.messages.getEnvelope(a.channelId,q)).state}catch{}return r.warn?.(`[im] runtime reply stream open outcome unknown requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId} reply_id=${q} reconciled_state=${s}: ${formatErrMessage(n)}; runtime_not_invoked`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_open_outcome_unknown",channelId:a.channelId,inboundMessageId:a.inboundMessageId}),i.unregister(_),!1}if(O)return r.info?.(`[im] runtime dispatch skipped existing deterministic reply requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"idempotent_reply_exists",channelId:a.channelId,inboundMessageId:a.inboundMessageId}),i.unregister(_),!1;const T=new SerializedCallbackQueue,A=createImReplySink({stream:k,openReasoningStream:()=>{const n=deterministicReplyMessageId("rsn",a.channelId,a.inboundMessageId,a.selfPrincipalId);return C=t.messages.startStream({conversationId:a.channelId,clientMessageId:n,idempotencyKey:n,replyTo:e.inReplyTo??a.inboundMessageId,type:"agent_reasoning",content:{}}),C},abortSignal:R.signal,callbackQueue:T,logger:r,requestId:u,runId:m,channelId:a.channelId,inboundMessageId:a.inboundMessageId,origin:I,onAgentRunStart:e=>{c.bindRun(w,e)}}),D=interceptAgentRuntimeFailures(A,"im_runtime",e=>r.warn?.(`[im] runtime failure reply suppressed requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId} error_code=${e.errorCode}`)),U=D.sink,K=async(e,n)=>{if(!C)return!1;try{return"cancel"===e?await C.cancel({stopReason:"user_stop"}):"fail"===e?await C.fail(n?{stopReason:n}:void 0):await C.finalize({stopReason:"end_turn"}),!1}catch(e){return r.warn?.(`[im] runtime reasoning stream terminal write failed requestId=${u} channel=${a.channelId}: ${formatErrMessage(e)}`),!0}};let N,F,L=!1;const Q=async e=>{U.dispatcher.markComplete(e);try{await U.dispatcher.waitForIdle()}catch{}},P=()=>{S();const n=normalizePromptTimeoutMs(e.abortTerminalGraceMs)??1e4;$=setTimeout(()=>{$=void 0,(async()=>{if(k.isTerminated)return;const e=R.signal.reason??E,n=void 0!==F,t=!n&&"string"==typeof e&&e.startsWith("chat_cancel");if(await Q("failure"),!k.isTerminated)try{t?(await K("cancel"),await k.cancel({stopReason:"user_stop"})):n?(await K("fail","timeout"),await failMessageStream(k,{body:F?.message??"IM runtime dispatch timed out",stopReason:"timeout"})):(await K("fail"),await failMessageStream(k,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"})),r.warn?.(`[im] abort settlement watchdog terminalized reply requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId} user_cancel=${t} timeout=${n}`)}catch(e){r.warn?.(`[im] abort settlement watchdog terminal write failed requestId=${u} channel=${a.channelId}: ${formatErrMessage(e)}`)}})()},n)};R.signal.aborted?P():R.signal.addEventListener("abort",P,{once:!0});const Y=()=>{if(!R.signal.aborted)return;const e=R.signal.reason;throw e instanceof Error?e:new Error(String(e??"runtime dispatch aborted"))};let G,j,z,V,B;const x=async e=>{if(!w)throw new Error("session_mutation_not_acquired");const n=await c.converge(w,e);return n.ok?(G=n.permit,!0):(r.error?.(`[im] session mutation recovery required requestId=${u} sessionKey=${f} reason=${n.recovery.reason}`),j={code:n.recovery.code,reason:n.recovery.reason,sessionKey:n.recovery.sessionKey,operationId:n.recovery.operationId,surface:"message_service",...n.recovery.terminalKind?{terminalKind:n.recovery.terminalKind}:{}},!1)};try{Y();const d=c.tryStartInteractive({sessionKey:f,label:"im_chat_message"},"message_service");if(!d.ok){try{await failMessageStream(k,{body:SESSION_BUSY_PUBLIC_MESSAGE,stopReason:"error"})}catch(e){r.warn?.(`[im] session busy terminal write failed channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}`)}return r.warn?.(`[im] session write gate busy sessionKey=${f} channel=${a.channelId} in_reply_to=${a.inboundMessageId} active_operation=${d.activeOperationId??"unknown"}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:"session_busy",cause:"runtime_error"}),!1}if(w=d.token,c.bindRun(w,m),V=s.register(f,A),B=o.register(f,A),l){const e=c.snapshot(f);z={runtimeConversationUid:a.channelId,runtimeSessionKey:a.channelId,historyGeneration:l.historyGeneration(a.channelId),targetMessageId:a.inboundMessageId,runtimeRunId:m,activeRunRegistrationId:`im:${e?.operationId??m}`},l.activeRuns.register(z,async()=>{const e=`chat_cancel:activeRun:${z.activeRunRegistrationId}`,n=await i.abort(_,e);return n.ok||"already_aborted"===n.reason?"accepted":"terminal_committing"===n.reason||"not_found"===n.reason?"already_terminal":"recovery_required"})}Y(),r.info?.(`[im] runtime dispatching requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId} origin=${I}`),e.onLifecycleEvent?.({type:"dispatch_start",requestId:u,channelId:a.channelId,inboundMessageId:a.inboundMessageId});const R=dispatchRuntimeReplyWithRoute({runtime:n,pluginConfig:e.pluginConfig,preparedPrompt:a.dispatchInput,replyRoute:g,replySinks:{im:U},onDispatchInvoked:()=>{L=!0}});void 0!==h&&(N=setTimeout(()=>{F=new Error(`IM runtime dispatch timed out after ${h}ms`),r.warn?.(`[im] runtime dispatch timeout requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId} timeout_ms=${h}`),v(F)},h));const E=Promise.race([R,M]),b=await E.finally(()=>{N&&(clearTimeout(N),N=void 0)}),$=D.detectedFailure();if($)throw new Error($.diagnosticMessage);if(Y(),await Q("success"),Y(),!i.claimTerminal(_,"completion")){if(Y(),"cancelling"===i.phase(_))throw new Error("cancellation in progress");throw new Error("terminal outcome already claimed")}if(!await x("completed"))return await K("fail"),await failMessageStream(k,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:"runtime_error",...j?{recovery:j}:{}}),!1;const S=U.dispatcher.getTerminalErrorText?.();if(void 0!==S){await K("fail");let n=!1;try{await failMessageStream(k,{body:S,stopReason:"error"})}catch(e){n=!0,r.warn?.(`[im] runtime reply stream error-terminal write outcome unknown requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}`)}return l&&z&&l.activeRuns.markTerminal(z,"failed"),r.warn?.(`[im] runtime dispatch ended with an error terminal requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId} counts=${JSON.stringify(b.counts)} terminalWriteFailed=${n} elapsed_ms=${Date.now()-p}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:"runtime_error_terminal_reply",cause:"runtime_error"}),n||await y("",S),!1}const q=await K("complete");let C=!1;try{await k.finalize({stopReason:"end_turn"})}catch(e){C=!0,r.warn?.(`[im] runtime reply stream finalize outcome unknown requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}`)}l&&z&&l.activeRuns.markTerminal(z,"completed");const O={...b.counts,source:A.getSourceCount()};r.info?.(`[im] runtime dispatch completed requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId} counts=${JSON.stringify(O)} elapsed_ms=${Date.now()-p}`),e.onLifecycleEvent?.({type:"dispatch_complete",requestId:u,channelId:a.channelId,inboundMessageId:a.inboundMessageId,counts:O,...C?{terminalWriteFailed:!0}:{},...q?{reasoningTerminalWriteFailed:!0}:{}});const T=U.dispatcher.getAccumulatedBody?.()??"";C||await y(T,"");const P="string"==typeof a.dispatchInput.message?a.dispatchInput.message:"";return maybeRenameImConversationTitle({client:t,conversationId:a.channelId,question:P,answer:T,logger:r}),!0}catch(n){await Q("failure");const t=R.signal.aborted,s=t?R.signal.reason:void 0,o="cancelling"===i.phase(_),c=void 0!==F,d=s??E,m=!c&&"string"==typeof d&&d.startsWith("chat_cancel");let p="runtime_error";c?p="timeout":(t||o)&&(p="abort");const I=o||m||c||t?"cancellation":"completion";if(!i.claimTerminal(_,I)){if(r.warn?.(`[im] terminal claim raced requestId=${u} channel=${a.channelId}; best-effort envelope terminal if still streaming`),!k.isTerminated)try{m?(await K("cancel"),await k.cancel({stopReason:"user_stop"})):c?(await K("fail","timeout"),await failMessageStream(k,{body:F?.message??formatErrMessage(n),stopReason:"timeout"})):(await K("fail"),await failMessageStream(k,{body:formatErrMessage(n),stopReason:"error"}))}catch(e){r.warn?.(`[im] raced terminal write failed requestId=${u}: ${formatErrMessage(e)}`)}throw n}const g=m?"cancelled":c?"timeout":"error";if(l&&z&&l.activeRuns.markTerminal(z,"cancelled"===g?"cancelled":"failed"),w&&!await x(g))throw m?(await K("cancel"),await k.cancel({stopReason:"user_stop"}).catch(()=>{})):(await K("fail"),await failMessageStream(k,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{})),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:p,...j?{recovery:j}:{}}),n;const h=classifyAgentRuntimeFailure({raw:n,source:"im_runtime",runtimeKind:"openclaw",requestAborted:m}),f=m?h:D.detectedFailure()??h;m?await K("cancel"):c?await K("fail","timeout"):await K("fail");let b=!1;try{m?await k.cancel({stopReason:"user_stop"}):await failMessageStream(k,{body:f.publicMessage,stopReason:messageStopReasonForRuntimeFailure(f)})}catch(e){b=!0,r.warn?.(`[im] runtime stream terminal write failed requestId=${u} channel=${a.channelId}: ${formatErrMessage(e)}`)}throw r.warn?.(`[im] runtime dispatch failed requestId=${u} channel=${a.channelId} in_reply_to=${a.inboundMessageId} cause=${p} dispatchInvoked=${L} error_code=${f.errorCode}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:u,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:f.errorCode,cause:p}),m||b||await y("",f.publicMessage),n}finally{S(),B?.(),V?.(),i.unregister(_),L?G&&c.release(G):c.releaseBeforeRuntime(w)}};
@@ -1 +1 @@
1
- import{toMessagePart}from"../message-client-types.js";import{resolveToolArtifactLinks}from"../upload/tool-artifact-enrichment.js";import{resolveRuntimeToolArguments,resolveRuntimeToolCallId,resolveRuntimeToolName}from"./tool-envelope-metadata.js";import{extractSearchSources,mergeSearchSource,searchSourceProvider}from"./search-source-extractor.js";import{logChatLatency}from"../chat-latency.js";const isNonEmptyString=e=>"string"==typeof e&&e.length>0,isPlainRecord=e=>"object"==typeof e&&null!==e&&!Array.isArray(e);function pickRawResultFromPayload(e){if(void 0!==e.rawResult)return e.rawResult;if(void 0!==e.result)return e.result;if(void 0!==e.details)return e.details;if(void 0!==e.toolResult)return e.toolResult;if(isPlainRecord(e.channelData)){const t=e.channelData;if(void 0!==t.rawResult)return t.rawResult;if(void 0!==t.result)return t.result;if(void 0!==t.details)return t.details}}export function createImReplySink(e){const t=e.startedAtMs??Date.now();let r=!1;const n=new Map,s=new Set,o={text:""};let a="",i="",l=!1,d=!1;const u=new Set,m=[];let g,c="",p="";const f=new Map,v=new Map,h=[],y=[],I=new Map,b=new Map;let S=0,$=1;const q=new Set,_=t=>{t!==o.text&&(t.startsWith(o.text)?e.stream.appendBody(t.slice(o.text.length)):e.stream.setBody(t),o.text=t)},x=t=>{if(void 0!==t.partIndex)return;const r=S,n=$;e.stream.addPart(toMessagePart({type:"text",text:t.text,state:"done",_seq:n})),t.partIndex=r,S+=1,$+=1},k=e=>{const t={text:e};m.push(t),d&&x(t)},w=(t,r)=>{for(const n of t){const t=b.get(n.url);if(t){const r=mergeSearchSource(t,n);if(r.title===t.title&&r.snippet===t.snippet)continue;const s={...r,partIndex:t.partIndex,displaySeq:t.displaySeq,toolUseId:t.toolUseId};e.stream.replacePart(t.partIndex,toMessagePart({type:"source",url:s.url,...s.title?{title:s.title}:{},...s.snippet?{snippet:s.snippet}:{},tool_use_id:s.toolUseId,_seq:s.displaySeq})),b.set(n.url,s);continue}const s={...n,partIndex:S,displaySeq:$,toolUseId:r};e.stream.addPart(toMessagePart({type:"source",url:n.url,...n.title?{title:n.title}:{},...n.snippet?{snippet:n.snippet}:{},tool_use_id:r,_seq:s.displaySeq})),b.set(n.url,s),S+=1,$+=1,K.source+=1}},E=e=>{const t=h.indexOf(e);t>=0&&h.splice(t,1)},R=e=>{const t=e?h.filter(t=>f.get(t)?.name===e):h;return 1===t.length?t[0]:void 0},O=e=>{const t=y.map((e,t)=>({hint:e,index:t})).filter(({hint:t})=>!e||t.name===e);if(1!==t.length)return;const[{hint:r,index:n}]=t;return y.splice(n,1),r},T=(e,t)=>{if(v.set(e,t),e===t)return;const r=I.get(e);if(!r)return;const n=A(t);U(n,r),I.delete(e);const s=f.get(t);s&&(Q(s,n),I.delete(t))},P=e=>v.get(e)??e,M=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),A=e=>{let t=I.get(e);return t||(t={},I.set(e,t)),t},C=(e,t)=>{if(!e?.length&&!t?.length)return;const r=[],n=new Map;for(const s of[...e??[],...t??[]]){const e=n.get(s.url);void 0===e?(n.set(s.url,r.length),r.push(s)):r[e]=mergeSearchSource(r[e],s)}return r},U=(e,t)=>{e.name??=t.name,e.arguments??=t.arguments,t.toolStartObserved&&(e.toolStartObserved=!0),t.itemEventObserved&&(e.itemEventObserved=!0),t.progress?.length&&(e.progress=[...e.progress??[],...t.progress]),e.sources=C(e.sources,t.sources)},N=(t,r,n)=>{const s="tool"===t.name&&"tool"!==r?r:t.name,o=!M(t.arguments)&&M(n)?n:t.arguments;s===t.name&&o===t.arguments||(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:s,arguments:o,state:t.state,_seq:t.displaySeq})),t.name=s,t.arguments=o)},j=(t,r,n,s)=>{const o=f.get(t);if(o)return N(o,r,n),s&&!h.includes(t)&&"streaming"===o.state&&h.push(t),o;(()=>{if(!d){d=!0;for(const e of m)x(e)}})();const a=I.get(t),i="tool"!==r?r:a?.name&&"tool"!==a.name?a.name:r,l=M(n)?n:a?.arguments??n,u=((t,r,n)=>{const s=S,o=$;return e.stream.addPart(toMessagePart({type:"tool_use",id:t,name:r,arguments:n,state:"streaming",_seq:o})),S+=1,$+=1,{partIndex:s,displaySeq:o}})(t,i,l),g={id:t,name:i,arguments:l,partIndex:u.partIndex,displaySeq:u.displaySeq,state:"streaming",itemEventObserved:!0===a?.itemEventObserved,toolStartObserved:!0===a?.toolStartObserved};return f.set(t,g),(s||g.toolStartObserved||g.itemEventObserved)&&(h.includes(t)||"streaming"!==g.state||h.push(t)),a&&Q(g,a),I.delete(t),g},L=(e,t)=>{const r=(e=>{const t=v.get(e);return t?f.get(t):f.get(e)})(e);if(r)return void 0===t.name&&void 0===t.arguments||N(r,t.name??r.name,t.arguments??r.arguments),t.toolStartObserved&&(r.toolStartObserved=!0),t.itemEventObserved&&(r.itemEventObserved=!0),(t.pending||t.toolStartObserved||t.itemEventObserved)&&!h.includes(r.id)&&"streaming"===r.state&&h.push(r.id),r;const n=A(e);void 0!==t.name&&(n.name=t.name),void 0!==t.arguments&&(n.arguments=t.arguments),t.toolStartObserved&&(n.toolStartObserved=!0),t.itemEventObserved&&(n.itemEventObserved=!0)},F=t=>{"done"!==t.state&&(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done",_seq:t.displaySeq})),t.state="done")},B=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(B).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${B(t)}`).join(",")}}`},D=(t,r,n)=>{const s=`${n?"1":"0"}:${B(r)}`;if(t.progressSignature===s)return;const o=toMessagePart({type:"custom",kind:"tool_progress",data:{tool_use_id:t.id,content:r,...n?{is_error:!0}:{}}});if(void 0===t.progressPartIndex){const r=S;return e.stream.addPart(o),t.progressPartIndex=r,t.progressSignature=s,void(S+=1)}e.stream.replacePart(t.progressPartIndex,o),t.progressSignature=s},Q=(e,t)=>{if(t.progress?.length){for(const r of t.progress)D(e,r.content,r.isError);F(e),E(e.id)}t.sources?.length&&w(t.sources,e.id)},z=e=>[e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e)),W=(e,t=[])=>{const r=[...new Set([...z(e),...t])];return isNonEmptyString(e.text)&&r.length>0?{text:e.text,media_urls:r}:isNonEmptyString(e.text)?e.text:1===r.length?r[0]:r.length>1?{media_urls:r}:!0===e.isError?"Tool execution failed.":""},J=(t,r)=>{if(t.durableUploadFilesEmitted)return;e.logger.info?.(`[im-sink] schedule tool artifact enrichment requestId=${e.requestId} toolUseId=${t.id} name=${t.name}`);const n=pickRawResultFromPayload(r),s=(async()=>{let s=[];try{s=await resolveToolArtifactLinks({toolCallId:t.id,toolName:t.name,rawResult:n,rawText:"string"==typeof r.text?r.text:void 0,payloadMediaUrls:z(r)})}catch(r){const n=r instanceof Error?r.message:String(r);return void e.logger.warn?.(`[im-sink] resolveToolArtifactLinks failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)}0!==s.length?(e.logger.info?.(`[im-sink] tool artifact enrichment resolved ${s.length} link(s) requestId=${e.requestId} toolUseId=${t.id}`),await new Promise(n=>{e.callbackQueue.enqueueSync(()=>{try{if(e.abortSignal.aborted||e.stream.isTerminated)return;const n=s.map(e=>e.uri);D(t,W(r,n),!0===r.isError),((t,r,n)=>{if(0!==r.length&&!t.durableUploadFilesEmitted){t.durableUploadFilesEmitted=!0,e.stream.appendToolResult(t.id,r.map(e=>({type:"resource_link",uri:e.uri,name:e.name,...e.mimeType?{mimeType:e.mimeType}:{}})),n),S+=1;for(const t of r)e.stream.addPart(toMessagePart({type:"resource_link",uri:t.uri,name:t.name,...t.mimeType?{mimeType:t.mimeType}:{}})),S+=1}})(t,s,!0===r.isError)}catch(r){const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool artifact enrichment apply failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)}finally{n()}})})):e.logger.debug?.(`[im-sink] tool artifact enrichment found no links requestId=${e.requestId} toolUseId=${t.id} name=${t.name}`)})().catch(r=>{const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool artifact enrichment failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)});q.add(s),s.finally(()=>{q.delete(s)})},G=(e,t,r)=>{const n=v.get(e);if(n)return L(n,{name:t,arguments:r});const s=L(e,{name:t,arguments:r});if(s)return s;const o=R(t);if(o)return T(e,o),L(o,{name:t,arguments:r});L(e,{name:t,arguments:r})},H=(e,t)=>{const r=v.get(e);if(r)return L(r,{name:t,itemEventObserved:!0,pending:!0});const n=L(e,{name:t,itemEventObserved:!0,pending:!0});if(n)return n;const s=(e=>{const t=h.filter(t=>{const r=f.get(t);return!(void 0===r||e&&r.name!==e||r.itemEventObserved)});return 1===t.length?t[0]:void 0})(t);if(s)return T(e,s),L(s,{name:t,itemEventObserved:!0,pending:!0});const o=O(t);L(e,{name:t,arguments:o?.arguments,itemEventObserved:!0,pending:!0})},K={tool:0,block:0,final:0,source:0};let V,X=!1;const Y=(t,r)=>{e.callbackQueue.enqueueSync(()=>{if((t=>e.abortSignal.aborted?(e.logger.debug?.(`[im-sink] ${t} ignored after abort requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):e.stream.isTerminated?(e.logger.debug?.(`[im-sink] ${t} ignored after stream_terminated requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):!X||(e.logger.debug?.(`[im-sink] ${t} ignored after mark_complete requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1))(t))try{r()}catch(r){const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] ${t} mapping failed requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId} error=${n}`)}})};return{dispatcher:{sendToolResult:t=>(Y("tool_progress",()=>{const r=resolveRuntimeToolCallId(t),n=resolveRuntimeToolName(t),s=resolveRuntimeToolArguments(t),o=W(t),a=!0===t.isError;let i;if(r){if(i=G(r,n,s),!i){const e=A(r);return n&&(e.name=n),M(s)&&(e.arguments=s),e.progress=[...e.progress??[],{content:o,isError:a}],void(K.tool+=1)}}else{const t=n?R(n):R();if(!t)return O(n),void e.logger.debug?.(`[im-sink] unattributed tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${n??"unknown"} pending=${h.length}`);i=L(t,{name:n,arguments:s})}i&&(K.tool+=1,D(i,o,a),F(i),E(i.id),J(i,t))}),!0),sendBlockReply:e=>(Y("block_reply",()=>{K.block+=1,isNonEmptyString(e.text)&&(V=!0!==e.isError?void 0:e.text)}),!0),sendFinalReply:e=>(Y("final_reply",()=>{var t;(K.final+=1,isNonEmptyString(e.text))&&(!0!==e.isError?(V=void 0,l||(t=e.text,i="",a+=t,_(a),k(t),l=!0)):V=e.text)}),!0),waitForIdle:async()=>{for(let t=0;t<16;t++){if(await e.callbackQueue.waitForIdle(),0===q.size)return;await Promise.allSettled([...q])}await e.callbackQueue.waitForIdle()},getQueuedCounts:()=>({tool:K.tool,block:K.block,final:K.final}),getAccumulatedBody:()=>o.text,getTerminalErrorText:()=>V,markComplete:t=>{(y.length>0||I.size>0)&&e.logger.debug?.(`[im-sink] dropping uncommitted lifecycle tool signals requestId=${e.requestId} legacy_hints=${y.length} buffered=${I.size}`),i="",_(a);for(const r of f.values())try{"success"===t&&F(r),E(r.id)}catch(t){const n=t instanceof Error?t.message:String(t);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${r.id} error=${n}`)}y.length=0,I.clear(),X=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,disableBlockStreaming:!0,onAgentRunStart:r=>{e.onAgentRunStart?.(r),logChatLatency(e.logger,"claw.runtime.run_started",e.requestId,t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${r} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:n=>{Y("partial",()=>{"string"==typeof n.text&&(!r&&n.text.length>0&&(r=!0,logChatLatency(e.logger,"claw.model.first_delta",e.requestId,t)),i=n.text,_(a+i))})},onReasoningStream:t=>{Y("reasoning",()=>{if(!isNonEmptyString(t.text))return;if(!g&&(g=e.openReasoningStream?.(),!g))return;const r=t.text;""===p&&""!==c&&(g.appendBody("\n"),c+="\n"),r!==p&&(r.startsWith(p)?(g.appendBody(r.slice(p.length)),p=r):p.startsWith(r)||(g.setBody(c+r),p=r))})},onReasoningEnd:()=>{Y("reasoning_end",()=>{var t;""!==p&&(t=p,e.stream.appendThinking(t,"done"),S+=1,c+=p,p="")})},onAssistantMessageStart:()=>{Y("assistant_message_start",()=>{i="",l=!1,_(a),e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)})},onToolStart:r=>{Y("tool_start",()=>{const s="string"==typeof r.phase?r.phase:"start",o=resolveRuntimeToolName(r)??"tool",a=resolveRuntimeToolCallId(r),i=resolveRuntimeToolArguments(r);if("update"===s)return void(a&&L(P(a),{name:o,arguments:i,pending:!0}));if(a&&n.set(a,Date.now()),logChatLatency(e.logger,"claw.tool.start",e.requestId,t,{tool:o}),a){L(a,{name:o,arguments:i,toolStartObserved:!0,pending:!0});const e=R(o);return void(e&&e!==a&&!v.has(a)&&(T(a,e),L(e,{name:o,arguments:i,toolStartObserved:!0,pending:!0})))}const l=(d=o,h.map(e=>f.get(e)).filter(e=>void 0!==e&&(!d||e.name===d)&&e.itemEventObserved));var d;if(1===l.length)return void L(l[0].id,{name:o,arguments:i,toolStartObserved:!0,pending:!0});if(l.length>1)return void e.logger.debug?.(`[im-sink] ambiguous id-less tool start ignored requestId=${e.requestId} channel=${e.channelId} tool=${o} candidates=${l.length}`);const u=[...I.entries()].filter(([,e])=>!e.name||e.name===o||"tool"===e.name);1!==u.length?y.push({name:o,arguments:i}):L(u[0][0],{name:o,arguments:i,toolStartObserved:!0,pending:!0})})},onItemEvent:e=>{Y("item_event",()=>{const t="string"==typeof e.kind?e.kind.toLowerCase():"";if(t&&"tool"!==t)return;const r="string"==typeof e.phase?e.phase.toLowerCase():"update",n=resolveRuntimeToolCallId(e)??("string"==typeof e.itemId&&e.itemId.trim()?e.itemId.trim():void 0),s=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);n&&s&&("update"!==r?H(n,s):L(P(n),{name:s,itemEventObserved:!0,pending:!0}))})}},commitAssistantMessage:e=>{Y("assistant_message_commit",()=>{if(u.has(e.key))return;u.add(e.key),l=!0,V=void 0,i="";const t=e.segments.filter(e=>"text"===e.type).map(e=>e.text).join("");a+=t,_(a);for(const t of e.segments)"text"!==t.type?j(t.id,t.name,t.arguments,!0):k(t.text)})},observeToolResult:r=>{Y("tool_result_observation",()=>{const o=r.toolCallId.trim(),a=r.toolName.trim();if(!o||!a)return;if(!s.has(o)){s.add(o);const i=n.get(o);logChatLatency(e.logger,"claw.tool.result",e.requestId,t,{tool:a,tool_elapsed_ms:void 0===i?void 0:Math.max(0,Date.now()-i),outcome:r.isError?"error":"success"})}const i=searchSourceProvider(r.rawResult)??"unknown";let l=0;try{const t=extractSearchSources({toolName:a,rawResult:r.rawResult,isError:r.isError});if(l=t.length,0===l)return;const n=G(o,a);if(n)w(t,n.id);else{const e=A(P(o));e.name??=a,e.sources=C(e.sources,t)}e.logger.debug?.(`[im-sink] search source projection requestId=${e.requestId} tool=${a} provider=${i} source_count=${l}`)}catch{e.logger.warn?.(`[im-sink] search source projection ignored requestId=${e.requestId} tool=${a} provider=${i} source_count=${l}`)}})},getSourceCount:()=>K.source}}
1
+ import{toMessagePart}from"../message-client-types.js";import{resolveToolArtifactLinks}from"../upload/tool-artifact-enrichment.js";import{resolveRuntimeToolArguments,resolveRuntimeToolCallId,resolveRuntimeToolName}from"./tool-envelope-metadata.js";import{extractSearchSources,mergeSearchSource,searchSourceProvider}from"./search-source-extractor.js";const isNonEmptyString=e=>"string"==typeof e&&e.length>0,isPlainRecord=e=>"object"==typeof e&&null!==e&&!Array.isArray(e);function pickRawResultFromPayload(e){if(void 0!==e.rawResult)return e.rawResult;if(void 0!==e.result)return e.result;if(void 0!==e.details)return e.details;if(void 0!==e.toolResult)return e.toolResult;if(isPlainRecord(e.channelData)){const t=e.channelData;if(void 0!==t.rawResult)return t.rawResult;if(void 0!==t.result)return t.result;if(void 0!==t.details)return t.details}}export function createImReplySink(e){const t={text:""};let r="",n="",s=!1,o=!1;const a=new Set,i=[];let l,d="",u="";const m=new Map,g=new Map,c=[],p=[],f=new Map,v=new Map;let y=0,I=1;const h=new Set,b=r=>{r!==t.text&&(r.startsWith(t.text)?e.stream.appendBody(r.slice(t.text.length)):e.stream.setBody(r),t.text=r)},S=t=>{if(void 0!==t.partIndex)return;const r=y,n=I;e.stream.addPart(toMessagePart({type:"text",text:t.text,state:"done",_seq:n})),t.partIndex=r,y+=1,I+=1},$=e=>{const t={text:e};i.push(t),o&&S(t)},q=(t,r)=>{for(const n of t){const t=v.get(n.url);if(t){const r=mergeSearchSource(t,n);if(r.title===t.title&&r.snippet===t.snippet)continue;const s={...r,partIndex:t.partIndex,displaySeq:t.displaySeq,toolUseId:t.toolUseId};e.stream.replacePart(t.partIndex,toMessagePart({type:"source",url:s.url,...s.title?{title:s.title}:{},...s.snippet?{snippet:s.snippet}:{},tool_use_id:s.toolUseId,_seq:s.displaySeq})),v.set(n.url,s);continue}const s={...n,partIndex:y,displaySeq:I,toolUseId:r};e.stream.addPart(toMessagePart({type:"source",url:n.url,...n.title?{title:n.title}:{},...n.snippet?{snippet:n.snippet}:{},tool_use_id:r,_seq:s.displaySeq})),v.set(n.url,s),y+=1,I+=1,D.source+=1}},x=e=>{const t=c.indexOf(e);t>=0&&c.splice(t,1)},_=e=>{const t=e?c.filter(t=>m.get(t)?.name===e):c;return 1===t.length?t[0]:void 0},k=e=>{const t=p.map((e,t)=>({hint:e,index:t})).filter(({hint:t})=>!e||t.name===e);if(1!==t.length)return;const[{hint:r,index:n}]=t;return p.splice(n,1),r},E=(e,t)=>{if(g.set(e,t),e===t)return;const r=f.get(e);if(!r)return;const n=O(t);P(n,r),f.delete(e);const s=m.get(t);s&&(F(s,n),f.delete(t))},R=e=>g.get(e)??e,w=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),O=e=>{let t=f.get(e);return t||(t={},f.set(e,t)),t},T=(e,t)=>{if(!e?.length&&!t?.length)return;const r=[],n=new Map;for(const s of[...e??[],...t??[]]){const e=n.get(s.url);void 0===e?(n.set(s.url,r.length),r.push(s)):r[e]=mergeSearchSource(r[e],s)}return r},P=(e,t)=>{e.name??=t.name,e.arguments??=t.arguments,t.toolStartObserved&&(e.toolStartObserved=!0),t.itemEventObserved&&(e.itemEventObserved=!0),t.progress?.length&&(e.progress=[...e.progress??[],...t.progress]),e.sources=T(e.sources,t.sources)},M=(t,r,n)=>{const s="tool"===t.name&&"tool"!==r?r:t.name,o=!w(t.arguments)&&w(n)?n:t.arguments;s===t.name&&o===t.arguments||(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:s,arguments:o,state:t.state,_seq:t.displaySeq})),t.name=s,t.arguments=o)},A=(t,r,n,s)=>{const a=m.get(t);if(a)return M(a,r,n),s&&!c.includes(t)&&"streaming"===a.state&&c.push(t),a;(()=>{if(!o){o=!0;for(const e of i)S(e)}})();const l=f.get(t),d="tool"!==r?r:l?.name&&"tool"!==l.name?l.name:r,u=w(n)?n:l?.arguments??n,g=((t,r,n)=>{const s=y,o=I;return e.stream.addPart(toMessagePart({type:"tool_use",id:t,name:r,arguments:n,state:"streaming",_seq:o})),y+=1,I+=1,{partIndex:s,displaySeq:o}})(t,d,u),p={id:t,name:d,arguments:u,partIndex:g.partIndex,displaySeq:g.displaySeq,state:"streaming",itemEventObserved:!0===l?.itemEventObserved,toolStartObserved:!0===l?.toolStartObserved};return m.set(t,p),(s||p.toolStartObserved||p.itemEventObserved)&&(c.includes(t)||"streaming"!==p.state||c.push(t)),l&&F(p,l),f.delete(t),p},U=(e,t)=>{const r=(e=>{const t=g.get(e);return t?m.get(t):m.get(e)})(e);if(r)return void 0===t.name&&void 0===t.arguments||M(r,t.name??r.name,t.arguments??r.arguments),t.toolStartObserved&&(r.toolStartObserved=!0),t.itemEventObserved&&(r.itemEventObserved=!0),(t.pending||t.toolStartObserved||t.itemEventObserved)&&!c.includes(r.id)&&"streaming"===r.state&&c.push(r.id),r;const n=O(e);void 0!==t.name&&(n.name=t.name),void 0!==t.arguments&&(n.arguments=t.arguments),t.toolStartObserved&&(n.toolStartObserved=!0),t.itemEventObserved&&(n.itemEventObserved=!0)},N=t=>{"done"!==t.state&&(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done",_seq:t.displaySeq})),t.state="done")},j=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(j).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${j(t)}`).join(",")}}`},C=(t,r,n)=>{const s=`${n?"1":"0"}:${j(r)}`;if(t.progressSignature===s)return;const o=toMessagePart({type:"custom",kind:"tool_progress",data:{tool_use_id:t.id,content:r,...n?{is_error:!0}:{}}});if(void 0===t.progressPartIndex){const r=y;return e.stream.addPart(o),t.progressPartIndex=r,t.progressSignature=s,void(y+=1)}e.stream.replacePart(t.progressPartIndex,o),t.progressSignature=s},F=(e,t)=>{if(t.progress?.length){for(const r of t.progress)C(e,r.content,r.isError);N(e),x(e.id)}t.sources?.length&&q(t.sources,e.id)},B=e=>[e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e)),L=(e,t=[])=>{const r=[...new Set([...B(e),...t])];return isNonEmptyString(e.text)&&r.length>0?{text:e.text,media_urls:r}:isNonEmptyString(e.text)?e.text:1===r.length?r[0]:r.length>1?{media_urls:r}:!0===e.isError?"Tool execution failed.":""},Q=(t,r)=>{if(t.durableUploadFilesEmitted)return;e.logger.info?.(`[im-sink] schedule tool artifact enrichment requestId=${e.requestId} toolUseId=${t.id} name=${t.name}`);const n=pickRawResultFromPayload(r),s=(async()=>{let s=[];try{s=await resolveToolArtifactLinks({toolCallId:t.id,toolName:t.name,rawResult:n,rawText:"string"==typeof r.text?r.text:void 0,payloadMediaUrls:B(r)})}catch(r){const n=r instanceof Error?r.message:String(r);return void e.logger.warn?.(`[im-sink] resolveToolArtifactLinks failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)}0!==s.length?(e.logger.info?.(`[im-sink] tool artifact enrichment resolved ${s.length} link(s) requestId=${e.requestId} toolUseId=${t.id}`),await new Promise(n=>{e.callbackQueue.enqueueSync(()=>{try{if(e.abortSignal.aborted||e.stream.isTerminated)return;const n=s.map(e=>e.uri);C(t,L(r,n),!0===r.isError),((t,r,n)=>{if(0!==r.length&&!t.durableUploadFilesEmitted){t.durableUploadFilesEmitted=!0,e.stream.appendToolResult(t.id,r.map(e=>({type:"resource_link",uri:e.uri,name:e.name,...e.mimeType?{mimeType:e.mimeType}:{}})),n),y+=1;for(const t of r)e.stream.addPart(toMessagePart({type:"resource_link",uri:t.uri,name:t.name,...t.mimeType?{mimeType:t.mimeType}:{}})),y+=1}})(t,s,!0===r.isError)}catch(r){const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool artifact enrichment apply failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)}finally{n()}})})):e.logger.debug?.(`[im-sink] tool artifact enrichment found no links requestId=${e.requestId} toolUseId=${t.id} name=${t.name}`)})().catch(r=>{const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool artifact enrichment failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)});h.add(s),s.finally(()=>{h.delete(s)})},z=(e,t,r)=>{const n=g.get(e);if(n)return U(n,{name:t,arguments:r});const s=U(e,{name:t,arguments:r});if(s)return s;const o=_(t);if(o)return E(e,o),U(o,{name:t,arguments:r});U(e,{name:t,arguments:r})},W=(e,t)=>{const r=g.get(e);if(r)return U(r,{name:t,itemEventObserved:!0,pending:!0});const n=U(e,{name:t,itemEventObserved:!0,pending:!0});if(n)return n;const s=(e=>{const t=c.filter(t=>{const r=m.get(t);return!(void 0===r||e&&r.name!==e||r.itemEventObserved)});return 1===t.length?t[0]:void 0})(t);if(s)return E(e,s),U(s,{name:t,itemEventObserved:!0,pending:!0});const o=k(t);U(e,{name:t,arguments:o?.arguments,itemEventObserved:!0,pending:!0})},D={tool:0,block:0,final:0,source:0};let J,G=!1;const H=(t,r)=>{e.callbackQueue.enqueueSync(()=>{if((t=>e.abortSignal.aborted?(e.logger.debug?.(`[im-sink] ${t} ignored after abort requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):e.stream.isTerminated?(e.logger.debug?.(`[im-sink] ${t} ignored after stream_terminated requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):!G||(e.logger.debug?.(`[im-sink] ${t} ignored after mark_complete requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1))(t))try{r()}catch(r){const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] ${t} mapping failed requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId} error=${n}`)}})};return{dispatcher:{sendToolResult:t=>(H("tool_progress",()=>{const r=resolveRuntimeToolCallId(t),n=resolveRuntimeToolName(t),s=resolveRuntimeToolArguments(t),o=L(t),a=!0===t.isError;let i;if(r){if(i=z(r,n,s),!i){const e=O(r);return n&&(e.name=n),w(s)&&(e.arguments=s),e.progress=[...e.progress??[],{content:o,isError:a}],void(D.tool+=1)}}else{const t=n?_(n):_();if(!t)return k(n),void e.logger.debug?.(`[im-sink] unattributed tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${n??"unknown"} pending=${c.length}`);i=U(t,{name:n,arguments:s})}i&&(D.tool+=1,C(i,o,a),N(i),x(i.id),Q(i,t))}),!0),sendBlockReply:e=>(H("block_reply",()=>{D.block+=1,isNonEmptyString(e.text)&&(J=!0!==e.isError?void 0:e.text)}),!0),sendFinalReply:e=>(H("final_reply",()=>{var t;(D.final+=1,isNonEmptyString(e.text))&&(!0!==e.isError?(J=void 0,s||(t=e.text,n="",r+=t,b(r),$(t),s=!0)):J=e.text)}),!0),waitForIdle:async()=>{for(let t=0;t<16;t++){if(await e.callbackQueue.waitForIdle(),0===h.size)return;await Promise.allSettled([...h])}await e.callbackQueue.waitForIdle()},getQueuedCounts:()=>({tool:D.tool,block:D.block,final:D.final}),getAccumulatedBody:()=>t.text,getTerminalErrorText:()=>J,markComplete:t=>{(p.length>0||f.size>0)&&e.logger.debug?.(`[im-sink] dropping uncommitted lifecycle tool signals requestId=${e.requestId} legacy_hints=${p.length} buffered=${f.size}`),n="",b(r);for(const r of m.values())try{"success"===t&&N(r),x(r.id)}catch(t){const n=t instanceof Error?t.message:String(t);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${r.id} error=${n}`)}p.length=0,f.clear(),G=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,disableBlockStreaming:!0,onAgentRunStart:t=>{e.onAgentRunStart?.(t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${t} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:e=>{H("partial",()=>{"string"==typeof e.text&&(n=e.text,b(r+n))})},onReasoningStream:t=>{H("reasoning",()=>{if(!isNonEmptyString(t.text))return;if(!l&&(l=e.openReasoningStream?.(),!l))return;const r=t.text;""===u&&""!==d&&(l.appendBody("\n"),d+="\n"),r!==u&&(r.startsWith(u)?(l.appendBody(r.slice(u.length)),u=r):u.startsWith(r)||(l.setBody(d+r),u=r))})},onReasoningEnd:()=>{H("reasoning_end",()=>{var t;""!==u&&(t=u,e.stream.appendThinking(t,"done"),y+=1,d+=u,u="")})},onAssistantMessageStart:()=>{H("assistant_message_start",()=>{n="",s=!1,b(r),e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)})},onToolStart:t=>{H("tool_start",()=>{const r="string"==typeof t.phase?t.phase:"start",n=resolveRuntimeToolName(t)??"tool",s=resolveRuntimeToolCallId(t),o=resolveRuntimeToolArguments(t);if("update"===r)return void(s&&U(R(s),{name:n,arguments:o,pending:!0}));if(s){U(s,{name:n,arguments:o,toolStartObserved:!0,pending:!0});const e=_(n);return void(e&&e!==s&&!g.has(s)&&(E(s,e),U(e,{name:n,arguments:o,toolStartObserved:!0,pending:!0})))}const a=(i=n,c.map(e=>m.get(e)).filter(e=>void 0!==e&&(!i||e.name===i)&&e.itemEventObserved));var i;if(1===a.length)return void U(a[0].id,{name:n,arguments:o,toolStartObserved:!0,pending:!0});if(a.length>1)return void e.logger.debug?.(`[im-sink] ambiguous id-less tool start ignored requestId=${e.requestId} channel=${e.channelId} tool=${n} candidates=${a.length}`);const l=[...f.entries()].filter(([,e])=>!e.name||e.name===n||"tool"===e.name);1!==l.length?p.push({name:n,arguments:o}):U(l[0][0],{name:n,arguments:o,toolStartObserved:!0,pending:!0})})},onItemEvent:e=>{H("item_event",()=>{const t="string"==typeof e.kind?e.kind.toLowerCase():"";if(t&&"tool"!==t)return;const r="string"==typeof e.phase?e.phase.toLowerCase():"update",n=resolveRuntimeToolCallId(e)??("string"==typeof e.itemId&&e.itemId.trim()?e.itemId.trim():void 0),s=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);n&&s&&("update"!==r?W(n,s):U(R(n),{name:s,itemEventObserved:!0,pending:!0}))})}},commitAssistantMessage:e=>{H("assistant_message_commit",()=>{if(a.has(e.key))return;a.add(e.key),s=!0,J=void 0,n="";const t=e.segments.filter(e=>"text"===e.type).map(e=>e.text).join("");r+=t,b(r);for(const t of e.segments)"text"!==t.type?A(t.id,t.name,t.arguments,!0):$(t.text)})},observeToolResult:t=>{H("tool_result_observation",()=>{const r=t.toolCallId.trim(),n=t.toolName.trim();if(!r||!n)return;const s=searchSourceProvider(t.rawResult)??"unknown";let o=0;try{const a=extractSearchSources({toolName:n,rawResult:t.rawResult,isError:t.isError});if(o=a.length,0===o)return;const i=z(r,n);if(i)q(a,i.id);else{const e=O(R(r));e.name??=n,e.sources=T(e.sources,a)}e.logger.debug?.(`[im-sink] search source projection requestId=${e.requestId} tool=${n} provider=${s} source_count=${o}`)}catch{e.logger.warn?.(`[im-sink] search source projection ignored requestId=${e.requestId} tool=${n} provider=${s} source_count=${o}`)}})},getSourceCount:()=>D.source}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.6.54-staging.4",
3
+ "version": "0.6.54",
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>",
@@ -67,7 +67,7 @@
67
67
  "dependencies": {
68
68
  "@beeos-ai/beeos-types": "4.4.0",
69
69
  "@beeos-ai/bridge-client": "^0.1.0",
70
- "@beeos-ai/message-sdk": "2.0.13",
70
+ "@beeos-ai/message-sdk": "2.0.10",
71
71
  "@modelcontextprotocol/sdk": "^1.29.0",
72
72
  "adm-zip": "^0.6.0",
73
73
  "ws": "^8.18.0"
@@ -87,5 +87,5 @@
87
87
  "fast-uri": ">=3.1.4",
88
88
  "brace-expansion": ">=5.0.9"
89
89
  },
90
- "gitHead": "f526dfe0c337b6d2946f65bf09c42ee165e78171"
90
+ "gitHead": "805f557d7206a624f8f96950b76bf76115c3db0f"
91
91
  }
@@ -1 +0,0 @@
1
- export const logChatLatency=(e,t,n,o,a={})=>{e.info?.(`[CHAT-LATENCY] ${JSON.stringify({component:"beeos-claw",event:t,request_id:n,elapsed_ms:Math.max(0,Date.now()-o),...a,ts:(new Date).toISOString()})}`)};