beeos-claw 0.6.32 → 0.6.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-gateway/background-run-im-egress.js +1 -1
- package/dist/acp-gateway/gateway-events.js +1 -1
- package/dist/acp-gateway/missing-artifacts-reconciler.js +1 -1
- package/dist/acp-gateway/session-state.js +1 -1
- package/dist/acp-gateway/tool-result-emitter.js +1 -1
- package/dist/im-runtime-dispatch.js +1 -1
- package/dist/runtime/im-reply-sink.js +1 -1
- package/package.json +1 -1
|
@@ -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())}
|
|
1
|
+
import{isSilentReplyPayloadText,startsWithSilentToken,stripHeartbeatToken,stripLeadingSilentToken,stripSilentToken}from"../auto-reply-tokens.js";import{toMessagePart}from"../message-client-types.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())}function asNonEmptyString(t){return"string"==typeof t&&t.trim()?t.trim():void 0}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),o=dedupeArtifacts(t.backgroundRunArtifacts??[]);if(!r&&0===o.length)return{publish:!1,reason:"no_publishable_content"};const i=t.inboundMessageId?.trim();return{publish:!0,egress:{conversationId:e,runId:n,text:r,artifacts:o,...i?{replyTo:i}:{}}}}export function dedupeArtifacts(t){const e=new Set,n=[];for(const r of t){const t=r?.uri?.trim();t&&!e.has(t)&&(/^(https?|beeos-file):\/\//i.test(t)&&(e.add(t),n.push(r)))}return n}export function captureBackgroundRunArtifacts(t,e){e?.length&&(!0!==t.backgroundRunAdopted&&!0!==t.backgroundRunEgressSettling||(t.backgroundRunArtifacts??=[]).push(...e))}export function trackBackgroundRunArtifactWork(t,e){const n=t.pendingArtifactWork??=new Set,r=e.then(()=>{},()=>{});n.add(r),r.then(()=>n.delete(r))}const ARTIFACT_SETTLE_MAX_MS=8e3;export async function awaitBackgroundRunArtifactQuiescence(t,e=8e3){const n=Date.now()+e;let r=0;for(;r<2&&Date.now()<n;){await new Promise(t=>setImmediate(t));const e=t.pendingArtifactWork;if(!e||0===e.size){r+=1;continue}let o;r=0;const i=new Promise(t=>{o=setTimeout(t,Math.max(0,n-Date.now()))});try{await Promise.race([Promise.allSettled([...e]),i])}finally{o&&clearTimeout(o)}}}export function backgroundRunEgressMessageId(t){return`bgrun_${t}`}export function backgroundRunFileParts(t){return t.map(t=>toMessagePart({type:"file",url:t.uri,...t.name?{name:t.name}:{},...t.mimeType?{mime_type:t.mimeType}:{}}))}
|
|
@@ -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,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}
|
|
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{awaitBackgroundRunArtifactQuiescence,backgroundRunEgressMessageId,backgroundRunFileParts,buildBackgroundRunImEgress,dedupeArtifacts,extractMessageToolCaption,trackBackgroundRunArtifactWork}from"./background-run-im-egress.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),trackBackgroundRunArtifactWork(t,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 c={sessionUpdate:"agent_message_chunk",content:{type:"text",text:l},...r?{timestamp:r}:{}};this.sendTurnUpdate(t,c),t.assistantMq?.push(c)}}}}else"delta"===e.state&&this.touchStreamActivity(t);if(this.maybeCompletePromptFromChatState(t,e),!t.promptDone)if("final"===e.state)this.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&&trackBackgroundRunArtifactWork(t,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.captureBackgroundRunCaption(t,a,r),n(buildToolCallPayload({toolCallId:e,toolName:a,args:r,timestamp:s}))}else if("result"===a)this.sendToolCallSpacer(t,s),t.hasAgentToolResultStream=!0,trackBackgroundRunArtifactWork(t,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.captureBackgroundRunCaption(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,c=a??r,d=n?.result;await this.emitNormalizedToolResult(e,{toolCallId:r,normalizeToolCallId:c,toolName:i,rawText:l,rawResult:d,timestamp:s})}async emitNormalizedToolResult(e,t){const s=t.pushUpdate??(t=>{this.sendTurnUpdate(e,t)&&e.assistantMq?.push(t)});await emitNormalizedToolResultForTurn({session:e,envelope:{toolCallId:t.toolCallId,toolName:t.toolName,rawText:t.rawText,rawResult:t.rawResult,timestamp:t.timestamp},normalizeToolCallId:t.normalizeToolCallId,turnId:resolveTurnId(e),config:this.config,logger:this.logger,writeObs:this.writeObs,getCanvasState:this.getCanvasState,sendUpdate:e=>(s(e),!0)})}adoptBackgroundRun(e,t){this.logger.debug?.(`[gateway-events] Adopting background run ${t} on session ${e.sessionId} (prev=${e.currentRunId})`);if(!this.adoptBackgroundMutation(e,t).ok)return this.observeLifecycle(this.abortGatewayRun(e.gatewaySessionKey,t),"background_run_conflict_abort",e),!1;e.currentRunId=t,e.promptDone=!1,this.state.promptsByRunId.set(t,e);const s=this.state.runIdToCronJobId.get(t);return s&&(e.cronJobId=s),e.assistantStreamStartedAt=Date.now(),e.lastStreamActivityTs=Date.now(),e.hasAgentAssistantStream=void 0,e.hasAgentThinkingStream=void 0,e.hasAgentToolStartStream=void 0,e.hasAgentToolResultStream=void 0,e.chatAssistantTextSoFar=void 0,e.agentAssistantTextSoFar=void 0,e.chatReplayDedupKeys?.clear(),e.presentedFileArtifactKeys=new Set,e.emittedToolResultKeys=new Set,e.backgroundRunAdopted=!0,e.backgroundRunArtifacts=[],e.assistantMq&&!e.assistantMq.closed||(e.assistantMq=new MessageQueue),this.schedulePromptTimeout(e),!0}maybePublishBackgroundRunToIm(e){const t=buildBackgroundRunImEgress(e);if(!t.publish&&"no_publishable_content"!==t.reason)return void("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"})`));e.backgroundRunAdopted=!1,e.backgroundRunCaption=void 0,e.backgroundRunEgressSettling=!0;const s=t.publish?t.egress:{conversationId:e.channelId.trim(),runId:e.currentRunId.trim(),text:"",artifacts:[],...e.inboundMessageId?.trim()?{replyTo:e.inboundMessageId.trim()}:{}};this.observeLifecycle(this.settleAndPublishBackgroundRun(e,s),"background_run_im_egress",e)}async settleAndPublishBackgroundRun(e,t){try{await awaitBackgroundRunArtifactQuiescence(e)}finally{e.backgroundRunEgressSettling=!1}const s=dedupeArtifacts([...t.artifacts,...e.backgroundRunArtifacts??[]]);e.backgroundRunArtifacts=void 0,t.text||0!==s.length?await this.publishBackgroundRunResult({...t,artifacts:s}):this.logger.debug?.(`[gateway-events] background run not published to IM (reason=no_publishable_content sessionId=${e.sessionId} runId=${t.runId})`)}captureBackgroundRunCaption(e,t,s){if(!0!==e.backgroundRunAdopted)return;const n=extractMessageToolCaption(t,s);n&&(e.backgroundRunCaption=n)}async publishBackgroundRunResult(e){const t=this.getMessageClient?.();if(!t)return;const s=backgroundRunFileParts(e.artifacts),n=backgroundRunEgressMessageId(e.runId);try{const o=t.messages.startStream({conversationId:e.conversationId,clientMessageId:n,idempotencyKey:n,type:"agent_reply",content:{background_run_id:e.runId},...e.replyTo?{replyTo:e.replyTo}:{}});await o.opened(),e.text&&o.setBody(e.text);for(const e of s)o.addPart(e);await o.finalize({stopReason:"end_turn"}),this.logger.info?.(`[gateway-events] published background run to conversation=${e.conversationId} run=${e.runId} fileParts=${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,filePartCount: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 c=encodeBeeosChannelTag(n,l);try{await this.gatewayRequest("cron.update",{id:t,patch:{description:c}}),this.setCronChannelCacheLocal(t,l),this.logger.info?.(`[gateway-events] backfilled beeos:channel for LLM-created cron job=${t} channel=${l}`)}catch(e){this.logger.warn?.(`[gateway-events] maybeBackfillBeeosChannel cron.update failed: ${e instanceof Error?e.message:e}`)}}setCronChannelCacheLocal(e,t){this.cronChannelCache&&this.cronChannelCache.set(e,t)}async publishCronResult(e){const t=this.getMessageClient?.();if(!t)return;const s=this.getMsgPrincipalId?.();if(!s)return;const 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{const s=t.messages.startStream({conversationId:a,clientMessageId:r,idempotencyKey:r,type:"agent_reply",content:{cron_job_id:n,..."string"==typeof e.sessionKey?{session_key:e.sessionKey}:{}}});await s.opened(),s.setBody(o),await s.finalize({stopReason:"end_turn"}),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 +1 @@
|
|
|
1
|
-
import{findLocalHistoryEntryByMessageId}from"./local-session-history.js";import{recordToolCallTurnForUpdate}from"./gateway-events.js";import{buildToolCallPayload}from"./tool-call-payload-builder.js";import{emitNormalizedToolResultForTurn,emitToolResultProjectionForTurn}from"./tool-result-emitter.js";import{collectToolCallsFromTurnWindow,collectToolResultsFromTurnWindow,findCurrentUserTurnStart,findLocalHistoryEntryIndexByMessageId,resolveToolArgs,resolveToolCallId}from"./tool-result-transcript.js";import{stripTransportMetadata}from"../message-sanitization/transport-metadata-sanitizer.js";export function reconcileMissingArtifacts(t,e){const{session:o,turnId:s,messageId:n,runId:r}=e;if(!o.agentId)return;const i=(o.toolCallTurns?.size??0)>0;o.hasAgentAssistantStream&&o.hasAgentThinkingStream&&o.hasAgentToolStartStream&&o.hasAgentToolResultStream&&!i
|
|
1
|
+
import{findLocalHistoryEntryByMessageId}from"./local-session-history.js";import{trackBackgroundRunArtifactWork}from"./background-run-im-egress.js";import{recordToolCallTurnForUpdate}from"./gateway-events.js";import{buildToolCallPayload}from"./tool-call-payload-builder.js";import{emitNormalizedToolResultForTurn,emitToolResultProjectionForTurn}from"./tool-result-emitter.js";import{collectToolCallsFromTurnWindow,collectToolResultsFromTurnWindow,findCurrentUserTurnStart,findLocalHistoryEntryIndexByMessageId,resolveToolArgs,resolveToolCallId}from"./tool-result-transcript.js";import{stripTransportMetadata}from"../message-sanitization/transport-metadata-sanitizer.js";export function reconcileMissingArtifacts(t,e){const{session:o,turnId:s,messageId:n,runId:r}=e;if(!o.agentId)return;const i=(o.toolCallTurns?.size??0)>0;if(o.hasAgentAssistantStream&&o.hasAgentThinkingStream&&o.hasAgentToolStartStream&&o.hasAgentToolResultStream&&!i)return;const a=(async()=>{if(await new Promise(t=>setImmediate(t)),t.isSessionAlive(o.sessionId))try{const e=t.localHistory.readEntries({agentId:o.agentId,sessionKey:o.gatewaySessionKey,sessionId:o.sessionId}),i=findLocalHistoryEntryByMessageId(e,n);if(!i)return;const a=findLocalHistoryEntryIndexByMessageId(e,n),l=Array.isArray(i.content)?i.content:[];if(0===l.length)return;const d="number"==typeof i.timestamp?new Date(i.timestamp).toISOString():"string"==typeof i.timestamp?i.timestamp:void 0;let c=0;const m=e=>{void 0===e._turnId&&(e._turnId=s),t.transport.sendSessionUpdate(o.sessionId,e,{turnId:s})&&(c+=1)};await reconcilePersistedToolResultsInTurn({deps:t,session:o,turnId:s,entries:e,currentIndex:a,timestamp:d,emit:m});for(const e of l){if(!isRecord(e))continue;const n=asString(e.type);if(n){if("text"===n&&!o.hasAgentAssistantStream){const t=asString(e.text);if(!t)continue;const o=stripTransportMetadata(t);if(!o)continue;m({sessionUpdate:"agent_message_chunk",content:{type:"text",text:o},...d?{timestamp:d}:{}});continue}if("thinking"===n&&!o.hasAgentThinkingStream){const t=asString(e.thinking)??asString(e.text);if(!t)continue;m({sessionUpdate:"agent_thought_chunk",content:{type:"text",text:t},...d?{timestamp:d}:{}});continue}if("toolCall"===n&&!o.hasAgentToolStartStream){const t=resolveToolCallId(e),n=asString(e.name)??asString(e.toolName)??asString(e.tool_name)??"tool",r=resolveToolArgs(e),i=buildToolCallPayload({toolCallId:t,toolName:n,args:r,timestamp:d});recordToolCallTurnForUpdate(o,i,s),m(i);continue}if("toolResult"===n&&!o.hasAgentToolResultStream){const n=resolveToolCallId(e),r=asString(e.name)??asString(e.toolName)??asString(e.tool_name)??"tool",i=e.result,a=asString(e.text)??asString(e.result)??(isRecord(i)||Array.isArray(i)?JSON.stringify(i,null,2):void 0);if(!a)continue;await emitNormalizedToolResultForTurn({session:o,envelope:{toolCallId:n,toolName:r,rawText:a,rawResult:i,...d?{timestamp:d}:{}},turnId:s,config:t.config,logger:t.logger,writeObs:t.writeObs,getCanvasState:t.getCanvasState,sendUpdate:t=>(m(t),!0)});continue}}}c>0&&(t.logger.info?.(`[missing-artifacts] reconciled ${c} block(s) sessionId=${o.sessionId} runId=${r??"n/a"} messageId=${n}`),t.writeObs({component:"bridge-core",domain:"lifecycle",name:"reconcile.missing_artifacts.emitted",severity:"info",payload:{sessionId:o.sessionId,runId:r,messageId:n,emitted:c}}))}catch(e){t.logger.warn?.(`[missing-artifacts] reconcile failed sessionId=${o.sessionId} messageId=${n}: ${e instanceof Error?e.message:String(e)}`),t.writeObs({component:"bridge-core",domain:"lifecycle",name:"reconcile.missing_artifacts.failed",severity:"warn",payload:{sessionId:o.sessionId,messageId:n,error:e instanceof Error?e.message:String(e)}})}else t.writeObs({component:"bridge-core",domain:"lifecycle",name:"reconcile.missing_artifacts.skipped_stale",severity:"debug",payload:{sessionId:o.sessionId,messageId:n,runId:r}})})();trackBackgroundRunArtifactWork(o,a)}async function reconcilePersistedToolResultsInTurn(t){const{deps:e,session:o,turnId:s,entries:n,currentIndex:r,timestamp:i,emit:a}=t;if(r<0)return 0;const l=findCurrentUserTurnStart(n,r);recordToolCallsFromTurnWindow(o,s,n.slice(l,r+1));let d=0;const c=collectToolResultsFromTurnWindow({entries:n,currentIndex:r,turnId:s,toolCallTurns:o.toolCallTurns,timestamp:i});for(const t of c){if(!t.toolCallId)continue;const n=d;await emitToolResultProjectionForTurn({session:o,projection:t,toolCallId:t.toolCallId,turnId:s,config:e.config,logger:e.logger,writeObs:e.writeObs,getCanvasState:e.getCanvasState,sendUpdate:t=>(a(t),d+=1,!0)}),d>n&&e.writeObs({component:"bridge-core",domain:"lifecycle",name:"reconcile.persisted_tool_result.emitted",severity:"info",payload:{sessionId:o.sessionId,toolCallId:t.toolCallId,toolName:t.toolName,projectionKey:t.projectionKey,emitted:d-n}})}return d}function recordToolCallsFromTurnWindow(t,e,o){for(const s of collectToolCallsFromTurnWindow(o))t.toolCallTurns??=new Map,t.toolCallTurns.has(s.toolCallId)||t.toolCallTurns.set(s.toolCallId,{turnId:e,toolName:s.toolName})}function isRecord(t){return!!t&&"object"==typeof t&&!Array.isArray(t)}function asString(t){if("string"==typeof t&&t.length>0)return t}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{MessageQueue}from"../utils/mq.js";import{isExternalInvocationSessionKey}from"./external-protocol-tags.js";const MAX_TOOL_CALL_MAP_ENTRIES=5e3,MAX_COMPLETED_RUN_IDS=200;export class AcpGatewaySessionState{sessions=new Map;promptsByRunId=new Map;promptsByGatewayRequestId=new Map;sessionsByGatewayKey=new Map;deletedGatewaySessionKeys=new Set;toolCallIdMap=new Map;toolCallToSessionMap=new Map;runIdToCronJobId=new Map;nextToolCallSeq=0;completedRunIds=new Set;lastActiveSessionId;sessionGcTimer=null;MAX_IDLE_SESSION_MS=864e5;EXTERNAL_SESSION_IDLE_MS=3e5;SESSION_GC_INTERVAL_MS=3e5;logger;constructor(e){this.logger=e}findSessionByKey(e){return this.sessionsByGatewayKey.get(e)}resolveSessionFromParams(e){const s="string"==typeof e?.sessionId?e.sessionId:void 0;if(s)return this.sessions.get(s)}isCurrentStreamOwner(e,s){return!s||!!e.currentRunId&&s===e.currentRunId}markRunCompleted(e){if(this.completedRunIds.add(e),this.completedRunIds.size>200){const e=this.completedRunIds.values().next().value;e&&this.completedRunIds.delete(e)}}isRunCompleted(e){return this.completedRunIds.has(e)}setRunCronJobId(e,s){if(this.runIdToCronJobId.set(e,s),this.runIdToCronJobId.size>400){const e=this.runIdToCronJobId.keys().next().value;void 0!==e&&this.runIdToCronJobId.delete(e)}}clearPromptTimeout(e){e.promptTimeoutTimer&&(clearTimeout(e.promptTimeoutTimer),e.promptTimeoutTimer=void 0)}clearStreamIdleTimer(e){e.streamIdleTimer&&(clearTimeout(e.streamIdleTimer),e.streamIdleTimer=void 0)}resetForNewRun(e,s,t){this.clearStreamIdleTimer(e),e.currentRunId&&(this.promptsByRunId.delete(e.currentRunId),this.runIdToCronJobId.delete(e.currentRunId)),e.gatewayRequestId&&this.promptsByGatewayRequestId.delete(e.gatewayRequestId),e.currentRunId=void 0,e.gatewayRequestId=void 0,e.assistantMq&&!e.assistantMq.closed&&(this.logger.warn?.(`[session-state] resetForNewRun discarding stale assistantMq sessionId=${s}`),e.assistantMq.close()),e.assistantMq=new MessageQueue,e.pendingUserTurnUpdates=[],e.chatReplayDedupKeys.clear(),e.hasAgentAssistantStream=!1,e.hasAgentThinkingStream=!1,e.hasAgentToolStartStream=!1,e.hasAgentToolResultStream=!1,e.chatAssistantTextSoFar=void 0,e.agentAssistantTextSoFar=void 0,e.cronJobId=void 0,e.presentedFileArtifactKeys=new Set,e.emittedToolResultKeys=new Set,e.backgroundRunAdopted=!1,e.backgroundRunArtifacts=void 0,e.backgroundRunCaption=void 0,e.promptDone=!1,t.resetSessionUpdateCount(s),this.enforceToolCallMapLimits()}enforceToolCallMapLimits(){if(this.toolCallIdMap.size>5e3){const e=this.toolCallIdMap.size-5e3;let s=0;for(const t of this.toolCallIdMap.keys()){if(s>=e)break;this.toolCallToSessionMap.delete(this.toolCallIdMap.get(t)),this.toolCallIdMap.delete(t),s++}}if(this.toolCallToSessionMap.size>5e3){const e=this.toolCallToSessionMap.size-5e3;let s=0;for(const t of this.toolCallToSessionMap.keys()){if(s>=e)break;this.toolCallToSessionMap.delete(t),s++}}}extractAgentAssistantDelta(e,s){const t=(e.chatAssistantTextSoFar??"").length;if(s.delta){const o=e.agentAssistantTextSoFar??"",i=o+s.delta;if(e.agentAssistantTextSoFar=i,t>0&&o.length<t){if(i.length<=t)return;return i.slice(t)}return s.delta}const o=s.text;if(!o)return;const i=e.agentAssistantTextSoFar??"";e.agentAssistantTextSoFar=o;const n=Math.max(i.length,t);if(!(o.length<=n)){if(o.startsWith(i)||0===n)return o.slice(n)||void 0;if(!i.startsWith(o))return o}}startSessionGc(e,s){this.sessionGcTimer=setInterval(()=>this.gcSessions(e,s),this.SESSION_GC_INTERVAL_MS),"object"==typeof this.sessionGcTimer&&"unref"in this.sessionGcTimer&&this.sessionGcTimer.unref()}gcSessions(e,s){const t=Date.now();let o=0;for(const[i,n]of this.sessions){if(void 0!==n.pendingPromptId)continue;(n.lastStreamActivityTs??n.createdAt)<t-(isExternalInvocationSessionKey(n.gatewaySessionKey)?this.EXTERNAL_SESSION_IDLE_MS:this.MAX_IDLE_SESSION_MS)&&(e(n),s?.(n),this.sessions.delete(i),this.sessionsByGatewayKey.delete(n.gatewaySessionKey),o++)}const i=new Set(Array.from(this.sessions.values()).map(e=>e.sessionId));for(const[e,s]of this.toolCallToSessionMap)i.has(s)||this.toolCallToSessionMap.delete(e);for(const[e,s]of this.toolCallIdMap)this.toolCallToSessionMap.has(s)||this.toolCallIdMap.delete(e);o>0&&(this.lastActiveSessionId&&!this.sessions.has(this.lastActiveSessionId)&&(this.lastActiveSessionId=void 0),this.logger.debug?.(`[session-state] GC evicted ${o} idle sessions, remaining=${this.sessions.size}`))}destroy(){this.sessionGcTimer&&(clearInterval(this.sessionGcTimer),this.sessionGcTimer=null);for(const e of this.sessions.values())this.clearPromptTimeout(e),this.clearStreamIdleTimer(e),e.assistantMq?.close();this.promptsByRunId.clear(),this.promptsByGatewayRequestId.clear(),this.runIdToCronJobId.clear(),this.completedRunIds.clear(),this.toolCallIdMap.clear(),this.toolCallToSessionMap.clear(),this.sessionsByGatewayKey.clear(),this.deletedGatewaySessionKeys.clear(),this.sessions.clear(),this.lastActiveSessionId=void 0}}
|
|
1
|
+
import{MessageQueue}from"../utils/mq.js";import{isExternalInvocationSessionKey}from"./external-protocol-tags.js";const MAX_TOOL_CALL_MAP_ENTRIES=5e3,MAX_COMPLETED_RUN_IDS=200;export class AcpGatewaySessionState{sessions=new Map;promptsByRunId=new Map;promptsByGatewayRequestId=new Map;sessionsByGatewayKey=new Map;deletedGatewaySessionKeys=new Set;toolCallIdMap=new Map;toolCallToSessionMap=new Map;runIdToCronJobId=new Map;nextToolCallSeq=0;completedRunIds=new Set;lastActiveSessionId;sessionGcTimer=null;MAX_IDLE_SESSION_MS=864e5;EXTERNAL_SESSION_IDLE_MS=3e5;SESSION_GC_INTERVAL_MS=3e5;logger;constructor(e){this.logger=e}findSessionByKey(e){return this.sessionsByGatewayKey.get(e)}resolveSessionFromParams(e){const s="string"==typeof e?.sessionId?e.sessionId:void 0;if(s)return this.sessions.get(s)}isCurrentStreamOwner(e,s){return!s||!!e.currentRunId&&s===e.currentRunId}markRunCompleted(e){if(this.completedRunIds.add(e),this.completedRunIds.size>200){const e=this.completedRunIds.values().next().value;e&&this.completedRunIds.delete(e)}}isRunCompleted(e){return this.completedRunIds.has(e)}setRunCronJobId(e,s){if(this.runIdToCronJobId.set(e,s),this.runIdToCronJobId.size>400){const e=this.runIdToCronJobId.keys().next().value;void 0!==e&&this.runIdToCronJobId.delete(e)}}clearPromptTimeout(e){e.promptTimeoutTimer&&(clearTimeout(e.promptTimeoutTimer),e.promptTimeoutTimer=void 0)}clearStreamIdleTimer(e){e.streamIdleTimer&&(clearTimeout(e.streamIdleTimer),e.streamIdleTimer=void 0)}resetForNewRun(e,s,t){this.clearStreamIdleTimer(e),e.currentRunId&&(this.promptsByRunId.delete(e.currentRunId),this.runIdToCronJobId.delete(e.currentRunId)),e.gatewayRequestId&&this.promptsByGatewayRequestId.delete(e.gatewayRequestId),e.currentRunId=void 0,e.gatewayRequestId=void 0,e.assistantMq&&!e.assistantMq.closed&&(this.logger.warn?.(`[session-state] resetForNewRun discarding stale assistantMq sessionId=${s}`),e.assistantMq.close()),e.assistantMq=new MessageQueue,e.pendingUserTurnUpdates=[],e.chatReplayDedupKeys.clear(),e.hasAgentAssistantStream=!1,e.hasAgentThinkingStream=!1,e.hasAgentToolStartStream=!1,e.hasAgentToolResultStream=!1,e.chatAssistantTextSoFar=void 0,e.agentAssistantTextSoFar=void 0,e.cronJobId=void 0,e.presentedFileArtifactKeys=new Set,e.emittedToolResultKeys=new Set,e.backgroundRunAdopted=!1,e.backgroundRunArtifacts=void 0,e.backgroundRunCaption=void 0,e.backgroundRunEgressSettling=!1,e.promptDone=!1,t.resetSessionUpdateCount(s),this.enforceToolCallMapLimits()}enforceToolCallMapLimits(){if(this.toolCallIdMap.size>5e3){const e=this.toolCallIdMap.size-5e3;let s=0;for(const t of this.toolCallIdMap.keys()){if(s>=e)break;this.toolCallToSessionMap.delete(this.toolCallIdMap.get(t)),this.toolCallIdMap.delete(t),s++}}if(this.toolCallToSessionMap.size>5e3){const e=this.toolCallToSessionMap.size-5e3;let s=0;for(const t of this.toolCallToSessionMap.keys()){if(s>=e)break;this.toolCallToSessionMap.delete(t),s++}}}extractAgentAssistantDelta(e,s){const t=(e.chatAssistantTextSoFar??"").length;if(s.delta){const o=e.agentAssistantTextSoFar??"",i=o+s.delta;if(e.agentAssistantTextSoFar=i,t>0&&o.length<t){if(i.length<=t)return;return i.slice(t)}return s.delta}const o=s.text;if(!o)return;const i=e.agentAssistantTextSoFar??"";e.agentAssistantTextSoFar=o;const n=Math.max(i.length,t);if(!(o.length<=n)){if(o.startsWith(i)||0===n)return o.slice(n)||void 0;if(!i.startsWith(o))return o}}startSessionGc(e,s){this.sessionGcTimer=setInterval(()=>this.gcSessions(e,s),this.SESSION_GC_INTERVAL_MS),"object"==typeof this.sessionGcTimer&&"unref"in this.sessionGcTimer&&this.sessionGcTimer.unref()}gcSessions(e,s){const t=Date.now();let o=0;for(const[i,n]of this.sessions){if(void 0!==n.pendingPromptId)continue;(n.lastStreamActivityTs??n.createdAt)<t-(isExternalInvocationSessionKey(n.gatewaySessionKey)?this.EXTERNAL_SESSION_IDLE_MS:this.MAX_IDLE_SESSION_MS)&&(e(n),s?.(n),this.sessions.delete(i),this.sessionsByGatewayKey.delete(n.gatewaySessionKey),o++)}const i=new Set(Array.from(this.sessions.values()).map(e=>e.sessionId));for(const[e,s]of this.toolCallToSessionMap)i.has(s)||this.toolCallToSessionMap.delete(e);for(const[e,s]of this.toolCallIdMap)this.toolCallToSessionMap.has(s)||this.toolCallIdMap.delete(e);o>0&&(this.lastActiveSessionId&&!this.sessions.has(this.lastActiveSessionId)&&(this.lastActiveSessionId=void 0),this.logger.debug?.(`[session-state] GC evicted ${o} idle sessions, remaining=${this.sessions.size}`))}destroy(){this.sessionGcTimer&&(clearInterval(this.sessionGcTimer),this.sessionGcTimer=null);for(const e of this.sessions.values())this.clearPromptTimeout(e),this.clearStreamIdleTimer(e),e.assistantMq?.close();this.promptsByRunId.clear(),this.promptsByGatewayRequestId.clear(),this.runIdToCronJobId.clear(),this.completedRunIds.clear(),this.toolCallIdMap.clear(),this.toolCallToSessionMap.clear(),this.sessionsByGatewayKey.clear(),this.deletedGatewaySessionKeys.clear(),this.sessions.clear(),this.lastActiveSessionId=void 0}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{presentFileArtifacts}from"./file-artifact-presenter.js";import{buildToolCallUpdatePayload}from"./tool-call-payload-builder.js";import{normalizeToolResultForAcpUpdate}from"./tool-result-payload-strategies.js";export async function emitNormalizedToolResultForTurn(t){const{session:e,envelope:o}=t;return emitToolResultProjectionForTurn({session:e,projection:{...(await normalizeToolResultForAcpUpdate({toolCallId:t.normalizeToolCallId??o.toolCallId,toolName:o.toolName,rawText:o.rawText,rawResult:o.rawResult})).projection,toolName:o.toolName,rawOutput:o.rawResult,...o.timestamp?{timestamp:o.timestamp}:{}},toolCallId:o.toolCallId,config:t.config,logger:t.logger,writeObs:t.writeObs,getCanvasState:t.getCanvasState,turnId:t.turnId,sendUpdate:t.sendUpdate})}export async function emitToolResultProjectionForTurn(t){const{session:e,projection:o,toolCallId:r}=t,
|
|
1
|
+
import{captureBackgroundRunArtifacts}from"./background-run-im-egress.js";import{presentFileArtifacts}from"./file-artifact-presenter.js";import{buildToolCallUpdatePayload}from"./tool-call-payload-builder.js";import{normalizeToolResultForAcpUpdate}from"./tool-result-payload-strategies.js";export async function emitNormalizedToolResultForTurn(t){const{session:e,envelope:o}=t;return emitToolResultProjectionForTurn({session:e,projection:{...(await normalizeToolResultForAcpUpdate({toolCallId:t.normalizeToolCallId??o.toolCallId,toolName:o.toolName,rawText:o.rawText,rawResult:o.rawResult})).projection,toolName:o.toolName,rawOutput:o.rawResult,...o.timestamp?{timestamp:o.timestamp}:{}},toolCallId:o.toolCallId,config:t.config,logger:t.logger,writeObs:t.writeObs,getCanvasState:t.getCanvasState,turnId:t.turnId,sendUpdate:t.sendUpdate})}export async function emitToolResultProjectionForTurn(t){const{session:e,projection:o,toolCallId:r}=t,a={emitted:!1,outputText:o.outputText,...o.resourceLinks?.length?{resourceLinks:o.resourceLinks}:{}};if(captureBackgroundRunArtifacts(e,o.resourceLinks),!o.outputText&&!o.resourceLinks?.length)return a;const s=`${r}:${o.projectionKey}`;if(e.emittedToolResultKeys??=new Set,e.emittedToolResultKeys.has(s))return a;const l=buildToolCallUpdatePayload({toolCallId:r,toolName:o.toolName,outputText:o.outputText,rawOutput:o.rawOutput,resourceLinks:o.resourceLinks,timestamp:o.timestamp});stampTurnId(l,t.turnId);return!1!==t.sendUpdate(l)?(e.emittedToolResultKeys.add(s),a.emitted=!0,t.writeObs({component:"acp-gateway",domain:"tool_result",name:"tool_result.normalized",severity:"info",payload:{toolCallId:r,toolName:o.toolName,hasText:Boolean(o.outputText),resourceLinkCount:o.resourceLinks?.length??0,projectionKey:o.projectionKey}}),o.resourceLinks?.length&&await presentFileArtifacts(e,r,o.resourceLinks,{config:t.config,logger:t.logger,writeObs:t.writeObs,getCanvasState:t.getCanvasState,sendUpdate:e=>{stampTurnId(e,t.turnId),t.sendUpdate(e)}}),a):a}function stampTurnId(t,e){e&&void 0===t._turnId&&(t._turnId=e)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash,randomUUID}from"node:crypto";import{dispatchRuntimeReplyWithRoute,hasRuntimeReplyApi}from"./runtime/reply-router.js";import{createImReplySink}from"./runtime/im-reply-sink.js";import{SerializedCallbackQueue}from"./runtime/callback-queue.js";import{promptRunKey}from"./runtime/prompt-run-store.js";import{classifyAgentRuntimeFailure}from"./runtime/agent-runtime-failure-classifier.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"./runtime/session-mutation-coordinator.js";import{interceptAgentRuntimeFailures,messageStopReasonForRuntimeFailure,SESSION_BUSY_PUBLIC_MESSAGE}from"./runtime/im-runtime-failure-policy.js";import{maybeRenameImConversationTitle}from"./runtime/conversation-title.js";const normalizePromptTimeoutMs=e=>{if("number"!=typeof e||!Number.isFinite(e))return;const n=Math.max(0,Math.trunc(e));return n>0?n:void 0},ABORT_TERMINAL_GRACE_MS=1e4,formatErrMessage=e=>{if(e instanceof Error)return e.message;if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}},failMessageStream=async(e,n)=>{void 0!==n.body&&e.setBody(n.body),await e.fail(void 0===n.stopReason?void 0:{stopReason:n.stopReason})};export const deterministicReplyMessageId=(e,n,t,a)=>`${e}_${createHash("sha256").update(`${n}:${t}:${a}`).digest("hex").slice(0,32)}`;export const dispatchImInboundPrompt=async e=>{const{runtime:n,prompt:t,client:a,logger:r,promptRunStore:i,sessionMutationCoordinator:s,abortTransport:o,activeRunAuthority:c}=e;if(!hasRuntimeReplyApi(n))return r.warn?.(`[im] runtime dispatch skipped reason=reply_api_unavailable channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_api_unavailable",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),!1;const d=`im_req_${randomUUID().replace(/-/g,"")}`,l=randomUUID(),u=Date.now(),m=e.origin??"im",p={protocol:"im",requestId:d,sessionId:t.sessionKey??"agent:main",sourceMessageId:t.inboundMessageId,chatId:t.channelId,origin:m},I=normalizePromptTimeoutMs(e.promptTimeoutMs),g=promptRunKey(t.channelId,t.inboundMessageId),h=t.sessionKey??"agent:main",y=new AbortController;let _,f,R;const E=new Promise((e,n)=>{R=n});let w;const b=()=>{void 0!==w&&(clearTimeout(w),w=void 0)},M=async e=>{if(f??=e,y.abort(e),!_)return{ok:!0};const n=await s.requestCancel(_,o);return n.ok?n.terminalObservation.then(e=>{e.ok||R(new Error(SESSION_RECOVERY_REQUIRED_CODE))}):R(new Error(SESSION_RECOVERY_REQUIRED_CODE)),{ok:n.ok}};if(!i.register(g,{requestId:d,channelId:t.channelId,requestCancel:M,startedAt:u,sessionKey:h}))return r.warn?.(`[im] runtime dispatch rejected duplicate inboundMessageId=${t.inboundMessageId} requestId=${d} channel=${t.channelId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"duplicate_inbound_message_id",cause:"abort"}),!1;const $=deterministicReplyMessageId("asst",t.channelId,t.inboundMessageId,t.selfPrincipalId),S=a.messages.startStream({conversationId:t.channelId,clientMessageId:$,idempotencyKey:$,replyTo:e.inReplyTo??t.inboundMessageId,type:"agent_reply",content:{}});let v,q=!1;try{q="duplicate"===(await S.opened()).outcome}catch(n){let s="unconfirmed";try{s=(await a.messages.getEnvelope(t.channelId,$)).state}catch{}return r.warn?.(`[im] runtime reply stream open outcome unknown requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} reply_id=${$} reconciled_state=${s}: ${formatErrMessage(n)}; runtime_not_invoked`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_open_outcome_unknown",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(g),!1}if(q)return r.info?.(`[im] runtime dispatch skipped existing deterministic reply requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"idempotent_reply_exists",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(g),!1;const k=new SerializedCallbackQueue,C=createImReplySink({stream:S,openReasoningStream:()=>{const n=deterministicReplyMessageId("rsn",t.channelId,t.inboundMessageId,t.selfPrincipalId);return v=a.messages.startStream({conversationId:t.channelId,clientMessageId:n,idempotencyKey:n,replyTo:e.inReplyTo??t.inboundMessageId,type:"agent_reasoning",content:{}}),v},abortSignal:y.signal,callbackQueue:k,logger:r,requestId:d,runId:l,channelId:t.channelId,inboundMessageId:t.inboundMessageId,origin:m,onAgentRunStart:e=>{s.bindRun(_,e)}}),O=interceptAgentRuntimeFailures(C,"im_runtime",e=>r.warn?.(`[im] runtime failure reply suppressed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} error_code=${e.errorCode}`)),T=O.sink,A=async(e,n)=>{if(!v)return!1;try{return"cancel"===e?await v.cancel({stopReason:"user_stop"}):"fail"===e?await v.fail(n?{stopReason:n}:void 0):await v.finalize({stopReason:"end_turn"}),!1}catch(e){return r.warn?.(`[im] runtime reasoning stream terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`),!0}};let D,U,K=!1;const N=async e=>{T.dispatcher.markComplete(e);try{await T.dispatcher.waitForIdle()}catch{}},F=()=>{b();const n=normalizePromptTimeoutMs(e.abortTerminalGraceMs)??1e4;w=setTimeout(()=>{w=void 0,(async()=>{if(S.isTerminated)return;const e=y.signal.reason??f,n=void 0!==U,a=!n&&"string"==typeof e&&e.startsWith("chat_cancel");if(await N("failure"),!S.isTerminated)try{a?(await A("cancel"),await S.cancel({stopReason:"user_stop"})):n?(await A("fail","timeout"),await failMessageStream(S,{body:U?.message??"IM runtime dispatch timed out",stopReason:"timeout"})):(await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"})),r.warn?.(`[im] abort settlement watchdog terminalized reply requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} user_cancel=${a} timeout=${n}`)}catch(e){r.warn?.(`[im] abort settlement watchdog terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`)}})()},n)};y.signal.aborted?F():y.signal.addEventListener("abort",F,{once:!0});const L=()=>{if(!y.signal.aborted)return;const e=y.signal.reason;throw e instanceof Error?e:new Error(String(e??"runtime dispatch aborted"))};let Q,P,Y;const G=async e=>{if(!_)throw new Error("session_mutation_not_acquired");const n=await s.converge(_,e);return n.ok?(Q=n.permit,!0):(r.error?.(`[im] session mutation recovery required requestId=${d} sessionKey=${h} reason=${n.recovery.reason}`),P={code:n.recovery.code,reason:n.recovery.reason,sessionKey:n.recovery.sessionKey,operationId:n.recovery.operationId,surface:"message_service",...n.recovery.terminalKind?{terminalKind:n.recovery.terminalKind}:{}},!1)};try{L();const o=s.tryStartInteractive({sessionKey:h,label:"im_chat_message"},"message_service");if(!o.ok){try{await failMessageStream(S,{body:SESSION_BUSY_PUBLIC_MESSAGE,stopReason:"error"})}catch(e){r.warn?.(`[im] session busy terminal write failed channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return r.warn?.(`[im] session write gate busy sessionKey=${h} channel=${t.channelId} in_reply_to=${t.inboundMessageId} active_operation=${o.activeOperationId??"unknown"}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"session_busy",cause:"runtime_error"}),!1}if(_=o.token,s.bindRun(_,l),c){const e=s.snapshot(h);Y={runtimeConversationUid:t.channelId,runtimeSessionKey:t.channelId,historyGeneration:c.historyGeneration(t.channelId),targetMessageId:t.inboundMessageId,runtimeRunId:l,activeRunRegistrationId:`im:${e?.operationId??l}`},c.activeRuns.register(Y,async()=>{const e=`chat_cancel:activeRun:${Y.activeRunRegistrationId}`,n=await i.abort(g,e);return n.ok||"already_aborted"===n.reason?"accepted":"terminal_committing"===n.reason||"not_found"===n.reason?"already_terminal":"recovery_required"})}L(),r.info?.(`[im] runtime dispatching requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} origin=${m}`),e.onLifecycleEvent?.({type:"dispatch_start",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId});const y=dispatchRuntimeReplyWithRoute({runtime:n,pluginConfig:e.pluginConfig,preparedPrompt:t.dispatchInput,replyRoute:p,replySinks:{im:T},onDispatchInvoked:()=>{K=!0}});void 0!==I&&(D=setTimeout(()=>{U=new Error(`IM runtime dispatch timed out after ${I}ms`),r.warn?.(`[im] runtime dispatch timeout requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} timeout_ms=${I}`),M(U)},I));const f=Promise.race([y,E]),R=await f.finally(()=>{D&&(clearTimeout(D),D=void 0)}),w=O.detectedFailure();if(w)throw new Error(w.diagnosticMessage);if(L(),await N("success"),L(),!i.claimTerminal(g,"completion")){if(L(),"cancelling"===i.phase(g))throw new Error("cancellation in progress");throw new Error("terminal outcome already claimed")}if(!await G("completed"))return await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:"runtime_error",...P?{recovery:P}:{}}),!1;const b=await A("complete");let $=!1;try{await S.finalize({stopReason:"end_turn"})}catch(e){$=!0,r.warn?.(`[im] runtime reply stream finalize outcome unknown requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}c&&Y&&c.activeRuns.markTerminal(Y,"completed"),r.info?.(`[im] runtime dispatch completed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(R.counts)} elapsed_ms=${Date.now()-u}`),e.onLifecycleEvent?.({type:"dispatch_complete",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,counts:R.counts,...$?{terminalWriteFailed:!0}:{},...b?{reasoningTerminalWriteFailed:!0}:{}});const v="string"==typeof t.dispatchInput.message?t.dispatchInput.message:"",q=T.dispatcher.getAccumulatedBody?.()??"";return maybeRenameImConversationTitle({client:a,conversationId:t.channelId,question:v,answer:q,logger:r}),!0}catch(n){await N("failure");const a=y.signal.aborted,s=a?y.signal.reason:void 0,o="cancelling"===i.phase(g),l=void 0!==U,u=s??f,m=!l&&"string"==typeof u&&u.startsWith("chat_cancel");let p="runtime_error";l?p="timeout":(a||o)&&(p="abort");const I=o||m||l||a?"cancellation":"completion";if(!i.claimTerminal(g,I)){if(r.warn?.(`[im] terminal claim raced requestId=${d} channel=${t.channelId}; best-effort envelope terminal if still streaming`),!S.isTerminated)try{m?(await A("cancel"),await S.cancel({stopReason:"user_stop"})):l?(await A("fail","timeout"),await failMessageStream(S,{body:U?.message??formatErrMessage(n),stopReason:"timeout"})):(await A("fail"),await failMessageStream(S,{body:formatErrMessage(n),stopReason:"error"}))}catch(e){r.warn?.(`[im] raced terminal write failed requestId=${d}: ${formatErrMessage(e)}`)}throw n}const h=m?"cancelled":l?"timeout":"error";if(c&&Y&&c.activeRuns.markTerminal(Y,"cancelled"===h?"cancelled":"failed"),_&&!await G(h))throw m?(await A("cancel"),await S.cancel({stopReason:"user_stop"}).catch(()=>{})):(await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{})),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:p,...P?{recovery:P}:{}}),n;const R=classifyAgentRuntimeFailure({raw:n,source:"im_runtime",runtimeKind:"openclaw",requestAborted:m}),E=m?R:O.detectedFailure()??R;m?await A("cancel"):l?await A("fail","timeout"):await A("fail");try{m?await S.cancel({stopReason:"user_stop"}):await failMessageStream(S,{body:E.publicMessage,stopReason:messageStopReasonForRuntimeFailure(E)})}catch(e){r.warn?.(`[im] runtime stream terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`)}throw r.warn?.(`[im] runtime dispatch failed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} cause=${p} dispatchInvoked=${K} error_code=${E.errorCode}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:E.errorCode,cause:p}),n}finally{b(),i.unregister(g),K?Q&&s.release(Q):s.releaseBeforeRuntime(_)}};
|
|
1
|
+
import{createHash,randomUUID}from"node:crypto";import{dispatchRuntimeReplyWithRoute,hasRuntimeReplyApi}from"./runtime/reply-router.js";import{createImReplySink}from"./runtime/im-reply-sink.js";import{SerializedCallbackQueue}from"./runtime/callback-queue.js";import{promptRunKey}from"./runtime/prompt-run-store.js";import{classifyAgentRuntimeFailure}from"./runtime/agent-runtime-failure-classifier.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"./runtime/session-mutation-coordinator.js";import{interceptAgentRuntimeFailures,messageStopReasonForRuntimeFailure,SESSION_BUSY_PUBLIC_MESSAGE}from"./runtime/im-runtime-failure-policy.js";import{maybeRenameImConversationTitle}from"./runtime/conversation-title.js";const normalizePromptTimeoutMs=e=>{if("number"!=typeof e||!Number.isFinite(e))return;const n=Math.max(0,Math.trunc(e));return n>0?n:void 0},ABORT_TERMINAL_GRACE_MS=1e4,formatErrMessage=e=>{if(e instanceof Error)return e.message;if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}},failMessageStream=async(e,n)=>{void 0!==n.body&&e.setBody(n.body),await e.fail(void 0===n.stopReason?void 0:{stopReason:n.stopReason})};export const deterministicReplyMessageId=(e,n,t,a)=>`${e}_${createHash("sha256").update(`${n}:${t}:${a}`).digest("hex").slice(0,32)}`;export const dispatchImInboundPrompt=async e=>{const{runtime:n,prompt:t,client:a,logger:r,promptRunStore:i,sessionMutationCoordinator:s,abortTransport:o,activeRunAuthority:c}=e;if(!hasRuntimeReplyApi(n))return r.warn?.(`[im] runtime dispatch skipped reason=reply_api_unavailable channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_api_unavailable",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),!1;const d=`im_req_${randomUUID().replace(/-/g,"")}`,l=randomUUID(),u=Date.now(),m=e.origin??"im",p={protocol:"im",requestId:d,sessionId:t.sessionKey??"agent:main",sourceMessageId:t.inboundMessageId,chatId:t.channelId,origin:m},I=normalizePromptTimeoutMs(e.promptTimeoutMs),g=promptRunKey(t.channelId,t.inboundMessageId),h=t.sessionKey??"agent:main",y=new AbortController;let _,f,R;const w=new Promise((e,n)=>{R=n});let E;const b=()=>{void 0!==E&&(clearTimeout(E),E=void 0)},M=async e=>{if(f??=e,y.abort(e),!_)return{ok:!0};const n=await s.requestCancel(_,o);return n.ok?n.terminalObservation.then(e=>{e.ok||R(new Error(SESSION_RECOVERY_REQUIRED_CODE))}):R(new Error(SESSION_RECOVERY_REQUIRED_CODE)),{ok:n.ok}};if(!i.register(g,{requestId:d,channelId:t.channelId,requestCancel:M,startedAt:u,sessionKey:h}))return r.warn?.(`[im] runtime dispatch rejected duplicate inboundMessageId=${t.inboundMessageId} requestId=${d} channel=${t.channelId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"duplicate_inbound_message_id",cause:"abort"}),!1;const $=deterministicReplyMessageId("asst",t.channelId,t.inboundMessageId,t.selfPrincipalId),S=a.messages.startStream({conversationId:t.channelId,clientMessageId:$,idempotencyKey:$,replyTo:e.inReplyTo??t.inboundMessageId,type:"agent_reply",content:{}});let v,q=!1;try{q="duplicate"===(await S.opened()).outcome}catch(n){let s="unconfirmed";try{s=(await a.messages.getEnvelope(t.channelId,$)).state}catch{}return r.warn?.(`[im] runtime reply stream open outcome unknown requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} reply_id=${$} reconciled_state=${s}: ${formatErrMessage(n)}; runtime_not_invoked`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_open_outcome_unknown",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(g),!1}if(q)return r.info?.(`[im] runtime dispatch skipped existing deterministic reply requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"idempotent_reply_exists",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(g),!1;const k=new SerializedCallbackQueue,C=createImReplySink({stream:S,openReasoningStream:()=>{const n=deterministicReplyMessageId("rsn",t.channelId,t.inboundMessageId,t.selfPrincipalId);return v=a.messages.startStream({conversationId:t.channelId,clientMessageId:n,idempotencyKey:n,replyTo:e.inReplyTo??t.inboundMessageId,type:"agent_reasoning",content:{}}),v},abortSignal:y.signal,callbackQueue:k,logger:r,requestId:d,runId:l,channelId:t.channelId,inboundMessageId:t.inboundMessageId,origin:m,onAgentRunStart:e=>{s.bindRun(_,e)}}),O=interceptAgentRuntimeFailures(C,"im_runtime",e=>r.warn?.(`[im] runtime failure reply suppressed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} error_code=${e.errorCode}`)),T=O.sink,A=async(e,n)=>{if(!v)return!1;try{return"cancel"===e?await v.cancel({stopReason:"user_stop"}):"fail"===e?await v.fail(n?{stopReason:n}:void 0):await v.finalize({stopReason:"end_turn"}),!1}catch(e){return r.warn?.(`[im] runtime reasoning stream terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`),!0}};let D,U,K=!1;const N=async e=>{T.dispatcher.markComplete(e);try{await T.dispatcher.waitForIdle()}catch{}},F=()=>{b();const n=normalizePromptTimeoutMs(e.abortTerminalGraceMs)??1e4;E=setTimeout(()=>{E=void 0,(async()=>{if(S.isTerminated)return;const e=y.signal.reason??f,n=void 0!==U,a=!n&&"string"==typeof e&&e.startsWith("chat_cancel");if(await N("failure"),!S.isTerminated)try{a?(await A("cancel"),await S.cancel({stopReason:"user_stop"})):n?(await A("fail","timeout"),await failMessageStream(S,{body:U?.message??"IM runtime dispatch timed out",stopReason:"timeout"})):(await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"})),r.warn?.(`[im] abort settlement watchdog terminalized reply requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} user_cancel=${a} timeout=${n}`)}catch(e){r.warn?.(`[im] abort settlement watchdog terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`)}})()},n)};y.signal.aborted?F():y.signal.addEventListener("abort",F,{once:!0});const L=()=>{if(!y.signal.aborted)return;const e=y.signal.reason;throw e instanceof Error?e:new Error(String(e??"runtime dispatch aborted"))};let Q,P,Y;const G=async e=>{if(!_)throw new Error("session_mutation_not_acquired");const n=await s.converge(_,e);return n.ok?(Q=n.permit,!0):(r.error?.(`[im] session mutation recovery required requestId=${d} sessionKey=${h} reason=${n.recovery.reason}`),P={code:n.recovery.code,reason:n.recovery.reason,sessionKey:n.recovery.sessionKey,operationId:n.recovery.operationId,surface:"message_service",...n.recovery.terminalKind?{terminalKind:n.recovery.terminalKind}:{}},!1)};try{L();const o=s.tryStartInteractive({sessionKey:h,label:"im_chat_message"},"message_service");if(!o.ok){try{await failMessageStream(S,{body:SESSION_BUSY_PUBLIC_MESSAGE,stopReason:"error"})}catch(e){r.warn?.(`[im] session busy terminal write failed channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return r.warn?.(`[im] session write gate busy sessionKey=${h} channel=${t.channelId} in_reply_to=${t.inboundMessageId} active_operation=${o.activeOperationId??"unknown"}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"session_busy",cause:"runtime_error"}),!1}if(_=o.token,s.bindRun(_,l),c){const e=s.snapshot(h);Y={runtimeConversationUid:t.channelId,runtimeSessionKey:t.channelId,historyGeneration:c.historyGeneration(t.channelId),targetMessageId:t.inboundMessageId,runtimeRunId:l,activeRunRegistrationId:`im:${e?.operationId??l}`},c.activeRuns.register(Y,async()=>{const e=`chat_cancel:activeRun:${Y.activeRunRegistrationId}`,n=await i.abort(g,e);return n.ok||"already_aborted"===n.reason?"accepted":"terminal_committing"===n.reason||"not_found"===n.reason?"already_terminal":"recovery_required"})}L(),r.info?.(`[im] runtime dispatching requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} origin=${m}`),e.onLifecycleEvent?.({type:"dispatch_start",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId});const y=dispatchRuntimeReplyWithRoute({runtime:n,pluginConfig:e.pluginConfig,preparedPrompt:t.dispatchInput,replyRoute:p,replySinks:{im:T},onDispatchInvoked:()=>{K=!0}});void 0!==I&&(D=setTimeout(()=>{U=new Error(`IM runtime dispatch timed out after ${I}ms`),r.warn?.(`[im] runtime dispatch timeout requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} timeout_ms=${I}`),M(U)},I));const f=Promise.race([y,w]),R=await f.finally(()=>{D&&(clearTimeout(D),D=void 0)}),E=O.detectedFailure();if(E)throw new Error(E.diagnosticMessage);if(L(),await N("success"),L(),!i.claimTerminal(g,"completion")){if(L(),"cancelling"===i.phase(g))throw new Error("cancellation in progress");throw new Error("terminal outcome already claimed")}if(!await G("completed"))return await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:"runtime_error",...P?{recovery:P}:{}}),!1;const b=T.dispatcher.getTerminalErrorText?.();if(void 0!==b){await A("fail");let n=!1;try{await failMessageStream(S,{body:b,stopReason:"error"})}catch(e){n=!0,r.warn?.(`[im] runtime reply stream error-terminal write outcome unknown requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return c&&Y&&c.activeRuns.markTerminal(Y,"failed"),r.warn?.(`[im] runtime dispatch ended with an error terminal requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(R.counts)} terminalWriteFailed=${n} elapsed_ms=${Date.now()-u}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"runtime_error_terminal_reply",cause:"runtime_error"}),!1}const $=await A("complete");let v=!1;try{await S.finalize({stopReason:"end_turn"})}catch(e){v=!0,r.warn?.(`[im] runtime reply stream finalize outcome unknown requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}c&&Y&&c.activeRuns.markTerminal(Y,"completed"),r.info?.(`[im] runtime dispatch completed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(R.counts)} elapsed_ms=${Date.now()-u}`),e.onLifecycleEvent?.({type:"dispatch_complete",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,counts:R.counts,...v?{terminalWriteFailed:!0}:{},...$?{reasoningTerminalWriteFailed:!0}:{}});const q="string"==typeof t.dispatchInput.message?t.dispatchInput.message:"",k=T.dispatcher.getAccumulatedBody?.()??"";return maybeRenameImConversationTitle({client:a,conversationId:t.channelId,question:q,answer:k,logger:r}),!0}catch(n){await N("failure");const a=y.signal.aborted,s=a?y.signal.reason:void 0,o="cancelling"===i.phase(g),l=void 0!==U,u=s??f,m=!l&&"string"==typeof u&&u.startsWith("chat_cancel");let p="runtime_error";l?p="timeout":(a||o)&&(p="abort");const I=o||m||l||a?"cancellation":"completion";if(!i.claimTerminal(g,I)){if(r.warn?.(`[im] terminal claim raced requestId=${d} channel=${t.channelId}; best-effort envelope terminal if still streaming`),!S.isTerminated)try{m?(await A("cancel"),await S.cancel({stopReason:"user_stop"})):l?(await A("fail","timeout"),await failMessageStream(S,{body:U?.message??formatErrMessage(n),stopReason:"timeout"})):(await A("fail"),await failMessageStream(S,{body:formatErrMessage(n),stopReason:"error"}))}catch(e){r.warn?.(`[im] raced terminal write failed requestId=${d}: ${formatErrMessage(e)}`)}throw n}const h=m?"cancelled":l?"timeout":"error";if(c&&Y&&c.activeRuns.markTerminal(Y,"cancelled"===h?"cancelled":"failed"),_&&!await G(h))throw m?(await A("cancel"),await S.cancel({stopReason:"user_stop"}).catch(()=>{})):(await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{})),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:p,...P?{recovery:P}:{}}),n;const R=classifyAgentRuntimeFailure({raw:n,source:"im_runtime",runtimeKind:"openclaw",requestAborted:m}),w=m?R:O.detectedFailure()??R;m?await A("cancel"):l?await A("fail","timeout"):await A("fail");try{m?await S.cancel({stopReason:"user_stop"}):await failMessageStream(S,{body:w.publicMessage,stopReason:messageStopReasonForRuntimeFailure(w)})}catch(e){r.warn?.(`[im] runtime stream terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`)}throw r.warn?.(`[im] runtime dispatch failed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} cause=${p} dispatchInvoked=${K} error_code=${w.errorCode}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:w.errorCode,cause:p}),n}finally{b(),i.unregister(g),K?Q&&s.release(Q):s.releaseBeforeRuntime(_)}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{toMessagePart}from"../message-client-types.js";import{resolveRuntimeToolArguments,resolveRuntimeToolCallId,resolveRuntimeToolName}from"./tool-envelope-metadata.js";const isNonEmptyString=e=>"string"==typeof e&&e.length>0,extractBodyUpdate=(e,t)=>{if(!isNonEmptyString(t))return{kind:"noop"};if(t===e.text)return{kind:"noop"};if(t.startsWith(e.text)){const n=t.slice(e.text.length);return e.text=t,{kind:"append",text:n}}return e.text.startsWith(t)?{kind:"noop"}:(e.text=t,{kind:"replace",text:t})};export function createImReplySink(e){const t={text:""};let n,r="",o="";const s=new Map,i=new Map,a=[],l=[];let d=0,u=0;const g=(t,n,r)=>{const o=u;return e.stream.appendToolUse(t,n,toMessagePart(r),"streaming"),u+=1,o},m=()=>(d+=1,`tool_${e.requestId}_${d}`),c=e=>{const t=a.indexOf(e);t>=0&&a.splice(t,1)},p=e=>{const t=e?a.filter(t=>s.get(t)?.name===e):a;return 1===t.length?t[0]:void 0},f=e=>{const t=l.map((e,t)=>({hint:e,index:t})).filter(({hint:t})=>!e||t.name===e);if(1!==t.length)return;const[{hint:n,index:r}]=t;return l.splice(r,1),n},h=(e,t)=>{i.set(e,t)},I=e=>i.get(e)??e,y=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),v=(t,n,r,o)=>{const i=s.get(t);if(i){const s="tool"===i.name&&"tool"!==n?n:i.name,l=!y(i.arguments)&&y(r)?r:i.arguments;return s===i.name&&l===i.arguments||(e.stream.replacePart(i.partIndex,toMessagePart({type:"tool_use",id:i.id,name:s,arguments:l,state:i.state})),i.name=s,i.arguments=l),o&&!a.includes(t)&&"streaming"===i.state&&a.push(t),i}const l={id:t,name:n,arguments:r,partIndex:g(t,n,r),state:"streaming",itemEventObserved:!1,toolStartObserved:!1};return s.set(t,l),o&&a.push(t),l}
|
|
1
|
+
import{toMessagePart}from"../message-client-types.js";import{resolveRuntimeToolArguments,resolveRuntimeToolCallId,resolveRuntimeToolName}from"./tool-envelope-metadata.js";const isNonEmptyString=e=>"string"==typeof e&&e.length>0,extractBodyUpdate=(e,t)=>{if(!isNonEmptyString(t))return{kind:"noop"};if(t===e.text)return{kind:"noop"};if(t.startsWith(e.text)){const n=t.slice(e.text.length);return e.text=t,{kind:"append",text:n}}return e.text.startsWith(t)?{kind:"noop"}:(e.text=t,{kind:"replace",text:t})};export function createImReplySink(e){const t={text:""};let n,r="",o="";const s=new Map,i=new Map,a=[],l=[];let d=0,u=0;const g=(t,n,r)=>{const o=u;return e.stream.appendToolUse(t,n,toMessagePart(r),"streaming"),u+=1,o},m=()=>(d+=1,`tool_${e.requestId}_${d}`),c=e=>{const t=a.indexOf(e);t>=0&&a.splice(t,1)},p=e=>{const t=e?a.filter(t=>s.get(t)?.name===e):a;return 1===t.length?t[0]:void 0},f=e=>{const t=l.map((e,t)=>({hint:e,index:t})).filter(({hint:t})=>!e||t.name===e);if(1!==t.length)return;const[{hint:n,index:r}]=t;return l.splice(r,1),n},h=(e,t)=>{i.set(e,t)},I=e=>i.get(e)??e,y=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),v=(t,n,r,o)=>{const i=s.get(t);if(i){const s="tool"===i.name&&"tool"!==n?n:i.name,l=!y(i.arguments)&&y(r)?r:i.arguments;return s===i.name&&l===i.arguments||(e.stream.replacePart(i.partIndex,toMessagePart({type:"tool_use",id:i.id,name:s,arguments:l,state:i.state})),i.name=s,i.arguments=l),o&&!a.includes(t)&&"streaming"===i.state&&a.push(t),i}const l={id:t,name:n,arguments:r,partIndex:g(t,n,r),state:"streaming",itemEventObserved:!1,toolStartObserved:!1};return s.set(t,l),o&&a.push(t),l},x=t=>{"done"!==t.state&&(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done"})),t.state="done")},$=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map($).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${$(t)}`).join(",")}}`},b=(e,t,n)=>{const r=i.get(e);if(r)return v(r,t??"tool",n??{},!1);if(s.has(e))return v(e,t??"tool",n??{},!1);const o=p(t);return o?(h(e,o),v(o,t??"tool",n??{},!1)):v(e,t??"tool",n??{},!1)},_=(e,t)=>{const n=i.get(e);if(n)return v(n,t,{},!1);if(s.has(e))return v(e,t,{},!1);const r=(e=>{const t=a.filter(t=>{const n=s.get(t);return!(void 0===n||e&&n.name!==e||n.itemEventObserved)});return 1===t.length?t[0]:void 0})(t);if(r)return h(e,r),v(r,t,{},!1);const o=f(t);return v(e,t,o?.arguments??{},!1)},k={tool:0,block:0,final:0};let S,R=!1;const E=(t,n)=>{e.callbackQueue.enqueueSync(()=>{if((t=>e.abortSignal.aborted?(e.logger.debug?.(`[im-sink] ${t} ignored after abort requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):e.stream.isTerminated?(e.logger.debug?.(`[im-sink] ${t} ignored after stream_terminated requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):!R||(e.logger.debug?.(`[im-sink] ${t} ignored after mark_complete requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1))(t))try{n()}catch(n){const r=n instanceof Error?n.message:String(n);e.logger.warn?.(`[im-sink] ${t} mapping failed requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId} error=${r}`)}})};return{dispatcher:{sendToolResult:t=>(E("tool_progress",()=>{const n=resolveRuntimeToolCallId(t),r=resolveRuntimeToolName(t),o=n?void 0:r?p(r):p(),i=n||o?void 0:f(r);if(!n&&!o&&!i)return void e.logger.debug?.(`[im-sink] ambiguous id-less tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${r??"unknown"} pending=${a.length}`);const l=n?b(n,r,resolveRuntimeToolArguments(t)):v(o??m(),r??s.get(o??"")?.name??i.name,y(resolveRuntimeToolArguments(t))?resolveRuntimeToolArguments(t):i?.arguments??{},!1);k.tool+=1,((t,n,r)=>{const o=`${r?"1":"0"}:${$(n)}`;if(t.progressSignature===o)return;const s=toMessagePart({type:"custom",kind:"tool_progress",data:{tool_use_id:t.id,content:n,...r?{is_error:!0}:{}}});if(void 0===t.progressPartIndex){const n=u;return e.stream.addPart(s),t.progressPartIndex=n,t.progressSignature=o,void(u+=1)}e.stream.replacePart(t.progressPartIndex,s),t.progressSignature=o})(l,(e=>{const t=[e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e));return isNonEmptyString(e.text)&&t.length>0?{text:e.text,media_urls:t}:isNonEmptyString(e.text)?e.text:1===t.length?t[0]:t.length>1?{media_urls:t}:!0===e.isError?"Tool execution failed.":""})(t),!0===t.isError),x(l),c(l.id)}),!0),sendBlockReply:n=>(E("block_reply",()=>{k.block+=1,isNonEmptyString(n.text)&&(!0!==n.isError?(S=void 0,e.stream.setBody(n.text),t.text=n.text):S=n.text)}),!0),sendFinalReply:n=>(E("final_reply",()=>{k.final+=1,isNonEmptyString(n.text)&&(!0!==n.isError?(S=void 0,e.stream.setBody(n.text),t.text=n.text):S=n.text)}),!0),waitForIdle:()=>e.callbackQueue.waitForIdle(),getQueuedCounts:()=>({...k}),getAccumulatedBody:()=>t.text,getTerminalErrorText:()=>S,markComplete:t=>{if(1===l.length){const e=l.shift();v(m(),e.name,e.arguments,!1)}for(const n of s.values())try{"success"===t&&x(n),c(n.id)}catch(t){const r=t instanceof Error?t.message:String(t);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${n.id} error=${r}`)}l.length=0,R=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,onAgentRunStart:t=>{e.onAgentRunStart?.(t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${t} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:n=>{E("partial",()=>{const r=extractBodyUpdate(t,n.text);switch(r.kind){case"append":e.stream.appendBody(r.text);break;case"replace":e.stream.setBody(r.text)}})},onReasoningStream:t=>{E("reasoning",()=>{if(!isNonEmptyString(t.text))return;if(!n&&(n=e.openReasoningStream?.(),!n))return;const s=t.text;""===o&&""!==r&&(n.appendBody("\n"),r+="\n"),s!==o&&(s.startsWith(o)?(n.appendBody(s.slice(o.length)),o=s):o.startsWith(s)||(n.setBody(r+s),o=s))})},onReasoningEnd:()=>{E("reasoning_end",()=>{var t;""!==o&&(t=o,e.stream.appendThinking(t,"done"),u+=1,r+=o,o="")})},onAssistantMessageStart:()=>{e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)},onToolStart:t=>{E("tool_start",()=>{const n="string"==typeof t.phase?t.phase:"start",r=resolveRuntimeToolName(t)??"tool",o=resolveRuntimeToolCallId(t);if("update"===n){if(o){const e=I(o);s.has(e)&&v(e,r,resolveRuntimeToolArguments(t),!0)}return}const i=resolveRuntimeToolArguments(t);if(o){const e=b(o,r,i);return e.toolStartObserved=!0,void(a.includes(e.id)||"streaming"!==e.state||a.push(e.id))}const d=(u=r,a.map(e=>s.get(e)).filter(e=>void 0!==e&&(!u||e.name===u)&&e.itemEventObserved));var u;if(1===d.length){return void(v(d[0].id,r,i,!0).toolStartObserved=!0)}d.length>1?e.logger.debug?.(`[im-sink] ambiguous id-less tool start ignored requestId=${e.requestId} channel=${e.channelId} tool=${r} candidates=${d.length}`):l.push({name:r,arguments:i})})},onItemEvent:e=>{E("item_event",()=>{const t="string"==typeof e.kind?e.kind.toLowerCase():"";if(t&&"tool"!==t)return;const n="string"==typeof e.phase?e.phase.toLowerCase():"update",r=resolveRuntimeToolCallId(e)??("string"==typeof e.itemId&&e.itemId.trim()?e.itemId.trim():void 0),o=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);if(!r||!o)return;if("update"===n){const e=I(r),t=s.get(e);return void(t&&(v(t.id,o,{},!0).itemEventObserved=!0))}const i=_(r,o);i.itemEventObserved=!0,a.includes(i.id)||"streaming"!==i.state||a.push(i.id)})}}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beeos-claw",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.34",
|
|
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>",
|