beeos-claw 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{createHash}from"node:crypto";import{Errors}from"../errors/index.js";import{formatFriendlyError}from"../errors/classify.js";import{stripTransportMetadata}from"../message-filter.js";import{resolveToolResultBlocks}from"./tool-result-payload-strategies.js";import{DEFAULT_STREAM_IDLE_TIMEOUT_MS,DEFAULT_STREAM_STALE_TIMEOUT_MS}from"./types.js";function asString(t){if("string"==typeof t){return t.trim()||void 0}}function isRecord(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function normalizeText(t){if("string"!=typeof t)return;return t.replace(/\r\n/g,"\n").replace(/\r/g,"\n")||void 0}const DEDUP_HASH_THRESHOLD=256;function dedupKey(t,...e){const s=`${t}:${e.join(":")}`;if(s.length<=256)return s;return`${t}:h:${createHash("sha256").update(s).digest("hex").slice(0,24)}`}export class AcpGatewayEvents{state;transport;config;logger;writeObs;completePrompt;failPrompt;maybeCompletePromptFromChatState;agentFetchFn;constructor(t){this.state=t.state,this.transport=t.transport,this.config=t.config,this.logger=t.logger,this.writeObs=t.writeObs,this.completePrompt=t.completePrompt,this.failPrompt=t.failPrompt,this.maybeCompletePromptFromChatState=t.maybeCompletePromptFromChatState,this.agentFetchFn=t.agentFetch}touchStreamActivity(t){t.lastStreamActivityTs=Date.now(),this.resetStreamIdleTimer(t)}resetStreamIdleTimer(t){this.state.clearStreamIdleTimer(t);const e=this.config.streamIdleTimeoutMs??DEFAULT_STREAM_IDLE_TIMEOUT_MS,s=this.config.streamStaleTimeoutMs??DEFAULT_STREAM_STALE_TIMEOUT_MS;t.streamIdleTimer=setTimeout(()=>{if(!t.lastStreamActivityTs)return;const e=Date.now()-t.lastStreamActivityTs;e>s&&(this.logger.warn?.(`[gateway-events] Stream stale timeout sessionId=${t.sessionId} stale=${e}ms`),this.completePrompt(t,"end_turn"))},e),"object"==typeof t.streamIdleTimer&&"unref"in t.streamIdleTimer&&t.streamIdleTimer.unref()}sendToolCallSpacer(t){const e={sessionUpdate:"agent_message_chunk",content:{type:"text",text:"\n"}};this.transport.sendSessionUpdate(t.sessionId,e),t.assistantMq?.push(e)}resolveToolCallId(t,e,s){const o=asString(e.toolCallId)??asString(e.tool_call_id)??asString(e.callId)??asString(e.id);if(o){const e=this.state.toolCallIdMap.get(o);return e?(t.sessionId&&this.state.toolCallToSessionMap.set(e,t.sessionId),e):(this.state.toolCallIdMap.set(o,o),t.sessionId&&this.state.toolCallToSessionMap.set(o,t.sessionId),o)}const n=`tc_${t.currentRunId??"unknown"}_${this.state.nextToolCallSeq++}`;return t.sessionId&&this.state.toolCallToSessionMap.set(n,t.sessionId),n}handleChatEvent(t){const e=(t.runId?this.state.promptsByRunId.get(t.runId):void 0)??this.state.findSessionByKey(t.sessionKey);if(!e)return;if(!this.state.isCurrentStreamOwner(e,t.runId)){if(!t.runId||!1===e.promptDone)return;this.adoptBackgroundRun(e,t.runId)}const s=e.sessionId,o=t.message;if(o){this.touchStreamActivity(e);if("assistant"===o.role){if(!Array.isArray(o.content))return;for(const n of o.content){const o=n;if(!isRecord(o))continue;const a=asString(o.type);if(a){if("thinking"===a){if(e.hasAgentThinkingStream)continue;const t=normalizeText(o.thinking)??normalizeText(o.text);if(!t)continue;const n=dedupKey("chat:thinking",t);if(e.chatReplayDedupKeys.has(n))continue;e.chatReplayDedupKeys.add(n);const a={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:t}};this.transport.sendSessionUpdate(s,a)&&e.assistantMq?.push(a);continue}if("toolCall"===a){if(this.sendToolCallSpacer(e),e.hasAgentToolStartStream)continue;const t=this.resolveToolCallId(e,o,"start"),n=asString(o.name)??asString(o.toolName)??asString(o.tool_name)??"tool",a=isRecord(o.arguments)?o.arguments:isRecord(o.args)?o.args:{},r=dedupKey("chat:tool_start",t,JSON.stringify(a));if(e.chatReplayDedupKeys.has(r))continue;e.chatReplayDedupKeys.add(r);const i={sessionUpdate:"tool_call",toolCallId:t,title:n,status:"in_progress",content:[{type:"content",content:{type:"text",text:JSON.stringify(a,null,2)}}]};this.transport.sendSessionUpdate(s,i)&&e.assistantMq?.push(i);continue}if("toolResult"===a){if(this.sendToolCallSpacer(e),e.hasAgentToolResultStream)continue;const t=this.resolveToolCallId(e,o,"result"),n=asString(o.name)??asString(o.toolName)??asString(o.tool_name)??"tool",a=normalizeText(o.text)??normalizeText(o.result)??(isRecord(o.result)||Array.isArray(o.result)?JSON.stringify(o.result,null,2):void 0);if(!a)continue;const r=dedupKey("chat:tool_result",t,a);if(e.chatReplayDedupKeys.has(r))continue;e.chatReplayDedupKeys.add(r);const i={sessionUpdate:"tool_call_update",toolCallId:t,title:n,status:"completed",content:[{type:"content",content:{type:"text",text:a}}]};this.transport.sendSessionUpdate(s,i)&&e.assistantMq?.push(i);continue}if("text"===a&&o.text){if(e.hasAgentAssistantStream||e.agentAssistantTextSoFar)continue;const n=stripTransportMetadata(o.text);if(!n)continue;const a=dedupKey("chat:text",n);if(e.chatReplayDedupKeys.has(a))continue;e.chatReplayDedupKeys.add(a);const r=e.chatAssistantTextSoFar??"";let i;if(e.chatAssistantTextSoFar=n,n.startsWith(r))i=n.slice(r.length);else{if(r.startsWith(n))continue;i=n}if(!i)continue;this.logger.debug?.(`[gateway-events] chat.text delta seq=${t.seq} runId=${t.runId} len=${i.length} prevLen=${r.length}`);const l={sessionUpdate:"agent_message_chunk",content:{type:"text",text:i}};this.transport.sendSessionUpdate(s,l),e.assistantMq?.push(l)}}}}}else"delta"===t.state&&this.touchStreamActivity(e);if(this.maybeCompletePromptFromChatState(e,t),!e.promptDone)if("final"===t.state)this.completePrompt(e,t.stopReason||"end_turn");else if("aborted"===t.state||"cancelled"===t.state||"cancel"===t.state)this.completePrompt(e,"cancelled");else if("error"===t.state){const s=t.errorMessage||Errors.AgentError.message;this.failPrompt(e,Errors.AgentError.rpcCode,formatFriendlyError(s))}}handleAgentEvent(t){const e=(t.runId?this.state.promptsByRunId.get(t.runId):void 0)??(t.sessionKey?this.state.findSessionByKey(t.sessionKey):void 0);if(!e)return;if(!this.state.isCurrentStreamOwner(e,t.runId)){if(!t.runId||!1===e.promptDone)return;this.adoptBackgroundRun(e,t.runId)}const s=e.sessionId;this.touchStreamActivity(e);const o=t=>{this.transport.sendSessionUpdate(s,t),e.assistantMq?.push(t)};if("assistant"===t.stream){e.hasAgentAssistantStream=!0;const n=this.state.extractAgentAssistantDelta(e,t.data??{});n&&(this.logger.debug?.(`[gateway-events] agent.assistant delta seq=${t.seq} runId=${t.runId} len=${n.length} source=${t.data?.delta?"delta":"cumulative"}`),o({sessionUpdate:"agent_message_chunk",content:{type:"text",text:n}}));const a=[],r=t.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 t of a){if(!isRecord(t))continue;const n=asString(t.type);if("thinking"===n){e.hasAgentThinkingStream=!0;const o=normalizeText(t.thinking)??normalizeText(t.text);if(o){const t={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:o}};this.transport.sendSessionUpdate(s,t)&&e.assistantMq?.push(t)}continue}if("toolCall"===n){this.sendToolCallSpacer(e),e.hasAgentToolStartStream=!0;const s=this.resolveToolCallId(e,t,"start"),n=asString(t.name)??asString(t.toolName)??asString(t.tool_name)??"tool",a=isRecord(t.arguments)?t.arguments:isRecord(t.args)?t.args:{};o({sessionUpdate:"tool_call",toolCallId:s,title:n,status:"in_progress",content:[{type:"content",content:{type:"text",text:JSON.stringify(a,null,2)}}]});continue}if("toolResult"===n){this.sendToolCallSpacer(e),e.hasAgentToolResultStream=!0;const s=this.resolveToolCallId(e,t,"result"),n=asString(t.name)??asString(t.toolName)??asString(t.tool_name)??"tool",a=normalizeText(t.text)??normalizeText(t.result)??(isRecord(t.result)||Array.isArray(t.result)?JSON.stringify(t.result,null,2):void 0);a&&o({sessionUpdate:"tool_call_update",toolCallId:s,title:n,status:"completed",content:[{type:"content",content:{type:"text",text:a}}]});continue}}}else if("thinking"===t.stream){e.hasAgentThinkingStream=!0;const o=normalizeText(t.data?.delta)??normalizeText(t.data?.text);if(o){const t={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:o}};this.transport.sendSessionUpdate(s,t)&&e.assistantMq?.push(t)}}else if("tool"===t.stream){const s=t.data,n="string"==typeof s?.phase?s.phase:void 0;if("start"===n){this.sendToolCallSpacer(e),e.hasAgentToolStartStream=!0;const t=this.resolveToolCallId(e,s,"start"),n=asString(s?.name)??asString(s?.toolName)??asString(s?.tool_name)??"tool",a=isRecord(s?.arguments)?s.arguments:isRecord(s?.args)?s.args:{};o({sessionUpdate:"tool_call",toolCallId:t,title:n,kind:"other",status:"in_progress",content:[{type:"content",content:{type:"text",text:JSON.stringify(a,null,2)}}]})}else if("result"===n)this.sendToolCallSpacer(e),e.hasAgentToolResultStream=!0,this.handleToolResult(e,t).catch(t=>{this.logger.warn?.(`[gateway-events] Tool result handling failed: ${t instanceof Error?t.message:t}`)});else{const t=s?.toolCall;if(t){this.sendToolCallSpacer(e),e.hasAgentToolStartStream=!0;const n=asString(t?.name)??asString(s?.name)??asString(s?.toolName)??"tool";o({sessionUpdate:"tool_call",toolCallId:this.resolveToolCallId(e,{...s,...t},"start"),title:n,kind:"other",status:"in_progress"})}}}else if("lifecycle"===t.stream){const s=t.data,o="string"==typeof s?.phase?s.phase:void 0;if("end"===o)this.completePrompt(e,"end_turn");else if("cancelled"===o||"cancel"===o)this.completePrompt(e,"cancelled");else if("error"===o){const o=s?.error,n=o&&"object"==typeof o?o:void 0,a=("string"==typeof s?.message?s.message:void 0)??("string"==typeof o?o:void 0)??("string"==typeof n?.message?n.message:void 0)??("string"==typeof s?.reason?s.reason:void 0)??(n?JSON.stringify(n):void 0)??"gateway lifecycle error",r=formatFriendlyError(a);this.logger.warn?.(`[gateway-events] lifecycle error runId=${t.runId} sessionKey=${t.sessionKey} message=${a} data=${JSON.stringify(s)}`),this.failPrompt(e,Errors.AgentError.rpcCode,r)}}}async handleToolResult(t,e){const s=e.data,o=s?.toolCall,n=("string"==typeof o?.id?o.id:void 0)??("string"==typeof s?.toolCallId?s.toolCallId:void 0),a=(n?this.state.toolCallIdMap.get(n):void 0)??n??`tc_${e.runId}_${e.seq}`,r=("string"==typeof o?.name?o.name:void 0)??("string"==typeof s?.name?s.name:void 0)??"tool",i="string"==typeof s?.text?s.text:void 0,l=n??a;let c;try{c=await resolveToolResultBlocks({toolCallId:l,toolName:r,rawText:i,rawResult:e.data})}catch{c=i?[{type:"content",content:{type:"text",text:i}}]:[]}if(0===c.length&&i&&(c=[{type:"content",content:{type:"text",text:i}}]),c.length>0){const e=c.filter(t=>"text"===t.content?.type&&t.content?.text).map(t=>({type:"content",content:{type:"text",text:t.content.text}})),s={sessionUpdate:"tool_call_update",toolCallId:a,title:r,kind:"other",status:"completed",...e.length>0?{content:e}:{}};this.transport.sendSessionUpdate(t.sessionId,s)&&t.assistantMq?.push(s)}}adoptBackgroundRun(t,e){this.logger.debug?.(`[gateway-events] Adopting background run ${e} on session ${t.sessionId} (prev=${t.currentRunId})`),t.currentRunId=e,t.promptDone=!1,this.state.promptsByRunId.set(e,t),t.assistantStreamStartedAt=Date.now(),t.lastStreamActivityTs=Date.now(),t.hasAgentAssistantStream=void 0,t.hasAgentThinkingStream=void 0,t.hasAgentToolStartStream=void 0,t.hasAgentToolResultStream=void 0,t.chatAssistantTextSoFar=void 0,t.agentAssistantTextSoFar=void 0,t.chatReplayDedupKeys?.clear()}handleCronEvent(t){if(this.transport.sendNotification("_beeos/cron_event",{payload:t}),this.config.agentGatewayUrl&&this.agentFetchFn){const e=`${this.config.agentGatewayUrl}/api/v1/agent/events`;this.agentFetchFn(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"cron",data:t})}).catch(t=>{this.logger.warn?.(`[gateway-events] event report failed: ${t instanceof Error?t.message:t}`)})}}}
1
+ import{createHash}from"node:crypto";import{Errors}from"../errors/index.js";import{formatFriendlyError}from"../errors/classify.js";import{stripTransportMetadata}from"../message-filter.js";import{resolveToolResultBlocks}from"./tool-result-payload-strategies.js";import{DEFAULT_STREAM_IDLE_TIMEOUT_MS,DEFAULT_STREAM_STALE_TIMEOUT_MS}from"./types.js";function asString(t){if("string"==typeof t){return t.trim()||void 0}}function isRecord(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function normalizeText(t){if("string"!=typeof t)return;return t.replace(/\r\n/g,"\n").replace(/\r/g,"\n")||void 0}const DEDUP_HASH_THRESHOLD=256;function dedupKey(t,...e){const s=`${t}:${e.join(":")}`;if(s.length<=256)return s;return`${t}:h:${createHash("sha256").update(s).digest("hex").slice(0,24)}`}function resolveToolArgs(t){const e=t.arguments??t.args??t.input;if(isRecord(e))return e;if("string"==typeof e){const t=e.trim();if(t.startsWith("{")||t.startsWith("["))try{return JSON.parse(t)}catch{}}return{}}function serializeArgs(t){return Object.keys(t).length>0?JSON.stringify(t,null,2):""}function formatToolTitle(t,e){const s=t??"tool";if(!e||0===Object.keys(e).length)return s;return`${s}: ${Object.entries(e).map(([t,e])=>{const s="string"==typeof e?e:JSON.stringify(e);return`${t}: ${s.length>100?`${s.slice(0,100)}...`:s}`}).join(", ")}`}function inferToolKind(t){if(!t)return"other";const e=t.toLowerCase();return e.includes("read")?"read":e.includes("write")||e.includes("edit")?"edit":e.includes("delete")||e.includes("remove")?"delete":e.includes("move")||e.includes("rename")?"move":e.includes("search")||e.includes("find")?"search":e.includes("exec")||e.includes("run")||e.includes("bash")?"execute":e.includes("fetch")||e.includes("http")?"fetch":"other"}function buildToolCallUpdate(t,e,s){const n=serializeArgs(s);return{sessionUpdate:"tool_call",toolCallId:t,title:formatToolTitle(e,s),kind:inferToolKind(e),status:"in_progress",rawInput:s,arguments:n,content:n?[{type:"content",content:{type:"text",text:n}}]:void 0}}export class AcpGatewayEvents{state;transport;config;logger;writeObs;completePrompt;failPrompt;maybeCompletePromptFromChatState;agentFetchFn;constructor(t){this.state=t.state,this.transport=t.transport,this.config=t.config,this.logger=t.logger,this.writeObs=t.writeObs,this.completePrompt=t.completePrompt,this.failPrompt=t.failPrompt,this.maybeCompletePromptFromChatState=t.maybeCompletePromptFromChatState,this.agentFetchFn=t.agentFetch}touchStreamActivity(t){t.lastStreamActivityTs=Date.now(),this.resetStreamIdleTimer(t)}resetStreamIdleTimer(t){this.state.clearStreamIdleTimer(t);const e=this.config.streamIdleTimeoutMs??DEFAULT_STREAM_IDLE_TIMEOUT_MS,s=this.config.streamStaleTimeoutMs??DEFAULT_STREAM_STALE_TIMEOUT_MS;t.streamIdleTimer=setTimeout(()=>{if(!t.lastStreamActivityTs)return;const e=Date.now()-t.lastStreamActivityTs;e>s&&(this.logger.warn?.(`[gateway-events] Stream stale timeout sessionId=${t.sessionId} stale=${e}ms`),this.completePrompt(t,"end_turn"))},e),"object"==typeof t.streamIdleTimer&&"unref"in t.streamIdleTimer&&t.streamIdleTimer.unref()}sendToolCallSpacer(t){const e={sessionUpdate:"agent_message_chunk",content:{type:"text",text:"\n"}};this.transport.sendSessionUpdate(t.sessionId,e),t.assistantMq?.push(e)}resolveToolCallId(t,e,s){const n=asString(e.toolCallId)??asString(e.tool_call_id)??asString(e.callId)??asString(e.id);if(n){const e=this.state.toolCallIdMap.get(n);return e?(t.sessionId&&this.state.toolCallToSessionMap.set(e,t.sessionId),e):(this.state.toolCallIdMap.set(n,n),t.sessionId&&this.state.toolCallToSessionMap.set(n,t.sessionId),n)}const o=`tc_${t.currentRunId??"unknown"}_${this.state.nextToolCallSeq++}`;return t.sessionId&&this.state.toolCallToSessionMap.set(o,t.sessionId),o}handleChatEvent(t){const e=(t.runId?this.state.promptsByRunId.get(t.runId):void 0)??this.state.findSessionByKey(t.sessionKey);if(!e)return;if(!this.state.isCurrentStreamOwner(e,t.runId)){if(!t.runId||!1===e.promptDone)return;this.adoptBackgroundRun(e,t.runId)}const s=e.sessionId,n=t.message;if(n){this.touchStreamActivity(e);if("assistant"===n.role){if(!Array.isArray(n.content))return;for(const o of n.content){const n=o;if(!isRecord(n))continue;const a=asString(n.type);if(a){if("thinking"===a){if(e.hasAgentThinkingStream)continue;const t=normalizeText(n.thinking)??normalizeText(n.text);if(!t)continue;const o=dedupKey("chat:thinking",t);if(e.chatReplayDedupKeys.has(o))continue;e.chatReplayDedupKeys.add(o);const a={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:t}};this.transport.sendSessionUpdate(s,a)&&e.assistantMq?.push(a);continue}if("toolCall"===a){if(this.sendToolCallSpacer(e),e.hasAgentToolStartStream)continue;const t=this.resolveToolCallId(e,n,"start"),o=asString(n.name)??asString(n.toolName)??asString(n.tool_name)??"tool",a=resolveToolArgs(n),r=dedupKey("chat:tool_start",t,JSON.stringify(a));if(e.chatReplayDedupKeys.has(r))continue;e.chatReplayDedupKeys.add(r);const i=buildToolCallUpdate(t,o,a);this.transport.sendSessionUpdate(s,i)&&e.assistantMq?.push(i);continue}if("toolResult"===a){if(this.sendToolCallSpacer(e),e.hasAgentToolResultStream)continue;const t=this.resolveToolCallId(e,n,"result"),o=asString(n.name)??asString(n.toolName)??asString(n.tool_name)??"tool",a=n.result,r=normalizeText(n.text)??normalizeText(n.result)??(isRecord(a)||Array.isArray(a)?JSON.stringify(a,null,2):void 0);if(!r)continue;const i=dedupKey("chat:tool_result",t,r);if(e.chatReplayDedupKeys.has(i))continue;e.chatReplayDedupKeys.add(i);const l={sessionUpdate:"tool_call_update",toolCallId:t,title:o,status:"completed",rawOutput:a,content:[{type:"content",content:{type:"text",text:r}}]};this.transport.sendSessionUpdate(s,l)&&e.assistantMq?.push(l);continue}if("text"===a&&n.text){if(e.hasAgentAssistantStream||e.agentAssistantTextSoFar)continue;const o=stripTransportMetadata(n.text);if(!o)continue;const a=dedupKey("chat:text",o);if(e.chatReplayDedupKeys.has(a))continue;e.chatReplayDedupKeys.add(a);const r=e.chatAssistantTextSoFar??"";let i;if(e.chatAssistantTextSoFar=o,o.startsWith(r))i=o.slice(r.length);else{if(r.startsWith(o))continue;i=o}if(!i)continue;this.logger.debug?.(`[gateway-events] chat.text delta seq=${t.seq} runId=${t.runId} len=${i.length} prevLen=${r.length}`);const l={sessionUpdate:"agent_message_chunk",content:{type:"text",text:i}};this.transport.sendSessionUpdate(s,l),e.assistantMq?.push(l)}}}}}else"delta"===t.state&&this.touchStreamActivity(e);if(this.maybeCompletePromptFromChatState(e,t),!e.promptDone)if("final"===t.state)this.completePrompt(e,t.stopReason||"end_turn");else if("aborted"===t.state||"cancelled"===t.state||"cancel"===t.state)this.completePrompt(e,"cancelled");else if("error"===t.state){const s=t.errorMessage||Errors.AgentError.message;this.failPrompt(e,Errors.AgentError.rpcCode,formatFriendlyError(s))}}handleAgentEvent(t){const e=(t.runId?this.state.promptsByRunId.get(t.runId):void 0)??(t.sessionKey?this.state.findSessionByKey(t.sessionKey):void 0);if(!e)return;if(!this.state.isCurrentStreamOwner(e,t.runId)){if(!t.runId||!1===e.promptDone)return;this.adoptBackgroundRun(e,t.runId)}const s=e.sessionId;this.touchStreamActivity(e);const n=t=>{this.transport.sendSessionUpdate(s,t),e.assistantMq?.push(t)};if("assistant"===t.stream){e.hasAgentAssistantStream=!0;const o=this.state.extractAgentAssistantDelta(e,t.data??{});o&&(this.logger.debug?.(`[gateway-events] agent.assistant delta seq=${t.seq} runId=${t.runId} len=${o.length} source=${t.data?.delta?"delta":"cumulative"}`),n({sessionUpdate:"agent_message_chunk",content:{type:"text",text:o}}));const a=[],r=t.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 t of a){if(!isRecord(t))continue;const o=asString(t.type);if("thinking"===o){e.hasAgentThinkingStream=!0;const n=normalizeText(t.thinking)??normalizeText(t.text);if(n){const t={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:n}};this.transport.sendSessionUpdate(s,t)&&e.assistantMq?.push(t)}continue}if("toolCall"===o){this.sendToolCallSpacer(e),e.hasAgentToolStartStream=!0;n(buildToolCallUpdate(this.resolveToolCallId(e,t,"start"),asString(t.name)??asString(t.toolName)??asString(t.tool_name)??"tool",resolveToolArgs(t)));continue}if("toolResult"===o){this.sendToolCallSpacer(e),e.hasAgentToolResultStream=!0;const s=this.resolveToolCallId(e,t,"result"),o=asString(t.name)??asString(t.toolName)??asString(t.tool_name)??"tool",a=t.result,r=normalizeText(t.text)??normalizeText(t.result)??(isRecord(a)||Array.isArray(a)?JSON.stringify(a,null,2):void 0);r&&n({sessionUpdate:"tool_call_update",toolCallId:s,title:o,status:"completed",rawOutput:a,content:[{type:"content",content:{type:"text",text:r}}]});continue}}}else if("thinking"===t.stream){e.hasAgentThinkingStream=!0;const n=normalizeText(t.data?.delta)??normalizeText(t.data?.text);if(n){const t={sessionUpdate:"agent_thought_chunk",content:{type:"text",text:n}};this.transport.sendSessionUpdate(s,t)&&e.assistantMq?.push(t)}}else if("tool"===t.stream){const s=t.data,o="string"==typeof s?.phase?s.phase:void 0;if("start"===o){this.sendToolCallSpacer(e),e.hasAgentToolStartStream=!0;n(buildToolCallUpdate(this.resolveToolCallId(e,s,"start"),asString(s?.name)??asString(s?.toolName)??asString(s?.tool_name)??"tool",resolveToolArgs(s)))}else if("result"===o)this.sendToolCallSpacer(e),e.hasAgentToolResultStream=!0,this.handleToolResult(e,t).catch(t=>{this.logger.warn?.(`[gateway-events] Tool result handling failed: ${t instanceof Error?t.message:t}`)});else{const t=s?.toolCall;if(t){this.sendToolCallSpacer(e),e.hasAgentToolStartStream=!0;const o=asString(t?.name)??asString(s?.name)??asString(s?.toolName)??"tool",a={...s,...t};n(buildToolCallUpdate(this.resolveToolCallId(e,a,"start"),o,resolveToolArgs(a)))}}}else if("lifecycle"===t.stream){const s=t.data,n="string"==typeof s?.phase?s.phase:void 0;if("end"===n)this.completePrompt(e,"end_turn");else if("cancelled"===n||"cancel"===n)this.completePrompt(e,"cancelled");else if("error"===n){const n=s?.error,o=n&&"object"==typeof n?n:void 0,a=("string"==typeof s?.message?s.message:void 0)??("string"==typeof n?n:void 0)??("string"==typeof o?.message?o.message:void 0)??("string"==typeof s?.reason?s.reason:void 0)??(o?JSON.stringify(o):void 0)??"gateway lifecycle error",r=formatFriendlyError(a);this.logger.warn?.(`[gateway-events] lifecycle error runId=${t.runId} sessionKey=${t.sessionKey} message=${a} data=${JSON.stringify(s)}`),this.failPrompt(e,Errors.AgentError.rpcCode,r)}}}async handleToolResult(t,e){const s=e.data,n=s?.toolCall,o=("string"==typeof n?.id?n.id:void 0)??("string"==typeof s?.toolCallId?s.toolCallId:void 0),a=(o?this.state.toolCallIdMap.get(o):void 0)??o??`tc_${e.runId}_${e.seq}`,r=("string"==typeof n?.name?n.name:void 0)??("string"==typeof s?.name?s.name:void 0)??"tool",i="string"==typeof s?.text?s.text:void 0,l=o??a;let c;try{c=await resolveToolResultBlocks({toolCallId:l,toolName:r,rawText:i,rawResult:e.data})}catch{c=i?[{type:"content",content:{type:"text",text:i}}]:[]}if(0===c.length&&i&&(c=[{type:"content",content:{type:"text",text:i}}]),c.length>0){const s=c.filter(t=>t.content&&(t.content.text||t.content.uri||t.content.data)).map(t=>({type:"content",content:t.content})),n={sessionUpdate:"tool_call_update",toolCallId:a,title:r,kind:inferToolKind(r),status:"completed",rawOutput:e.data,...s.length>0?{content:s}:{}};this.transport.sendSessionUpdate(t.sessionId,n)&&t.assistantMq?.push(n)}}adoptBackgroundRun(t,e){this.logger.debug?.(`[gateway-events] Adopting background run ${e} on session ${t.sessionId} (prev=${t.currentRunId})`),t.currentRunId=e,t.promptDone=!1,this.state.promptsByRunId.set(e,t),t.assistantStreamStartedAt=Date.now(),t.lastStreamActivityTs=Date.now(),t.hasAgentAssistantStream=void 0,t.hasAgentThinkingStream=void 0,t.hasAgentToolStartStream=void 0,t.hasAgentToolResultStream=void 0,t.chatAssistantTextSoFar=void 0,t.agentAssistantTextSoFar=void 0,t.chatReplayDedupKeys?.clear()}handleCronEvent(t){if(this.transport.sendNotification("_beeos/cron_event",{payload:t}),this.config.agentGatewayUrl&&this.agentFetchFn){const e=`${this.config.agentGatewayUrl}/api/v1/agent/events`;this.agentFetchFn(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"cron",data:t})}).catch(t=>{this.logger.warn?.(`[gateway-events] event report failed: ${t instanceof Error?t.message:t}`)})}}}
@@ -1 +1 @@
1
- import{stripTransportMetadata}from"../message-filter.js";const BEEOS_USER_MSG_MARKER_RE=/(?:^|\n)\s*User Message From \w+:\s*\n/i,CANVAS_STATE_RE=/^\[Canvas State\][^\n]*/,SYSTEM_NOTE_RE=/^\[System Note\][^\n]*/,CANVAS_INTERACTION_RE=/^\[Canvas Interaction\] The user clicked "([^"]*)" on surface "([^"]*)"/,ACTION_CONTEXT_RE=/\nAction context: (.+?)(?:\n|$)/s;function stripBeeosUserEnvelope(t){const e=BEEOS_USER_MSG_MARKER_RE.exec(t);if(e){return t.slice(e.index+e[0].length).trim()}let o=t;return o=o.replace(CANVAS_STATE_RE,"").trimStart(),o=o.replace(SYSTEM_NOTE_RE,"").trimStart(),stripTransportMetadata(o).trim()}export async function loadHistory(t,e,o=50){if(!t?.isConnected)return[];try{const n=parseGatewayHistory(await t.chatHistory(e,o));if(n.length>0){n.filter(t=>"user"===t.role).length,n.filter(t=>"assistant"===t.role).length}return n}catch(t){return[]}}export function groupIntoTurns(t){const e=[];let o=null;const n=()=>{o&&(e.push(o),o=null)};for(const s of t){if("assistant"===s.role&&(s.reasoning||s.toolCalls?.length)){n(),o={role:"assistant",content:s.content,reasoning:s.reasoning,files:s.files},s.toolCalls?.length&&(o.toolCalls=s.toolCalls.map(t=>({id:t.id,callId:t.id,name:t.name,arguments:t.arguments??"",status:t.status??"completed",output:t.output,isError:"error"===t.status||"failed"===t.status})));continue}if("system"===s.role){n();const t={role:"system",content:s.content};s.metadata&&(t.metadata=s.metadata),e.push(t);continue}if("user"===s.role){n();const t={role:"user",content:s.content};s.provenance&&(t.provenance=s.provenance),s.metadata&&(t.metadata=s.metadata),e.push(t);continue}o||(o={role:"assistant",content:""});switch(s.updateType??"agent_message_chunk"){case"agent_thought_chunk":o.reasoning=(o.reasoning??"")+s.content;break;case"tool_call":o.toolCalls||(o.toolCalls=[]),o.toolCalls.push({id:s.toolCallId??`tc_${o.toolCalls.length}`,callId:s.toolCallId??`tc_${o.toolCalls.length}`,name:s.toolName??"tool",arguments:s.content||"",status:"completed"});break;case"tool_call_update":{o.toolCalls||(o.toolCalls=[]);const t=s.toolCallId?o.toolCalls.find(t=>t.id===s.toolCallId):void 0;t?(t.output=s.content,t.status="completed"):o.toolCalls.push({id:s.toolCallId??`tc_${o.toolCalls.length}`,callId:s.toolCallId??`tc_${o.toolCalls.length}`,name:s.toolName??"tool",arguments:"",status:"completed",output:s.content});break}default:o.content+=s.content,s.files?.length&&(o.files=[...o.files??[],...s.files])}}return n(),e}const TOOL_CALL_BLOCK_TYPES=new Set(["toolCall","toolUse","functionCall","tool_use","tool_call","toolcall"]),TOOL_RESULT_BLOCK_TYPES=new Set(["tool_result","tool_result_error","toolResult"]);function extractBlockText(t){if("string"==typeof t)return t;if(!Array.isArray(t))return"";let e="";for(const o of t)"string"!=typeof o?o&&"object"==typeof o&&"string"==typeof o.text&&(e+=o.text):e+=o;return e}function parseGatewayHistory(t){if(!t||"object"!=typeof t)return[];const e=t.messages;if(!Array.isArray(e))return[];const o=[];let n=null;for(const t of e){if(!t||"object"!=typeof t)continue;const e=t,s="string"==typeof e.role?e.role:"assistant";if("toolResult"===s||"tool"===s){if(!n?.toolCalls?.length)continue;const t=String(e.toolCallId??e.tool_call_id??e.toolUseId??e.tool_use_id??""),o=extractBlockText(e.content);if(t){const s=n.toolCalls.find(e=>e.id===t);if(s)s.output=o,!0!==e.is_error&&!0!==e.isError||(s.status="error");else{const t=n.toolCalls.find(t=>void 0===t.output);t&&(t.output=o,!0!==e.is_error&&!0!==e.isError||(t.status="error"))}}else{const t=n.toolCalls.find(t=>void 0===t.output);t&&(t.output=o,!0!==e.is_error&&!0!==e.isError||(t.status="error"))}continue}if("user"===s&&n?.toolCalls?.length&&Array.isArray(e.content)){let t=!1,o=!1;for(const n of e.content){if(!n||"object"!=typeof n)continue;const e=n,s="string"==typeof e.type?e.type:"";TOOL_RESULT_BLOCK_TYPES.has(s)?t=!0:"text"===s&&"string"==typeof e.text&&e.text.trim()&&(o=!0)}if(t&&!o){for(const t of e.content){if(!t||"object"!=typeof t)continue;const e=t,o="string"==typeof e.type?e.type:"";if(!TOOL_RESULT_BLOCK_TYPES.has(o))continue;const s=String(e.toolUseId??e.tool_use_id??e.toolCallId??e.tool_call_id??""),r="string"==typeof e.output?e.output:extractBlockText(e.content);if(s){const t=n.toolCalls.find(t=>t.id===s);if(t)t.output=r,!0!==e.is_error&&!0!==e.isError||(t.status="error");else{const t=n.toolCalls.find(t=>void 0===t.output);t&&(t.output=r,!0!==e.is_error&&!0!==e.isError||(t.status="error"))}}else{const t=n.toolCalls.find(t=>void 0===t.output);t&&(t.output=r,!0!==e.is_error&&!0!==e.isError||(t.status="error"))}}continue}}const r="user"===s?"user":"assistant";let i,l,a,c,u,p="";if("string"==typeof e.content)p=e.content;else if(Array.isArray(e.content))for(const t of e.content){if("string"==typeof t){p+=t;continue}if(!t||"object"!=typeof t)continue;const e=t,o="string"==typeof e.type?e.type:"";if("text"===o&&"string"==typeof e.text)p+=e.text;else if("thinking"===o&&"string"==typeof e.thinking)l=(l??"")+e.thinking;else if("resource_link"===o&&"string"==typeof e.uri&&e.uri)a||(a=[]),a.push({uri:e.uri,..."string"==typeof e.name&&e.name?{name:e.name}:{},..."string"==typeof e.mimeType&&e.mimeType?{mimeType:e.mimeType}:{}});else if(TOOL_CALL_BLOCK_TYPES.has(o)){i||(i=[]);const t=e.arguments??e.input,o="string"==typeof t?t:null!=t?JSON.stringify(t):"";i.push({id:String(e.id??e.toolCallId??e.tool_call_id??""),name:String(e.name??"tool"),arguments:o,status:"completed"})}else if(TOOL_RESULT_BLOCK_TYPES.has(o)){const t=String(e.toolUseId??e.tool_use_id??e.toolCallId??e.tool_call_id??""),o="string"==typeof e.output?e.output:extractBlockText(e.content),n=!0===e.is_error||!0===e.isError;if(i){const e=t?i.find(e=>e.id===t):void 0;if(e)e.output=o,n&&(e.status="error");else{const t=i.find(t=>void 0===t.output);t&&(t.output=o,n&&(t.status="error"))}}}}if(l||("string"==typeof e.reasoning&&e.reasoning?l=e.reasoning:"string"==typeof e.thinking&&e.thinking&&(l=e.thinking)),!i?.length){const t=e.tool_calls??e.toolCalls;if(Array.isArray(t)){i=[];for(const e of t){if(!e||"object"!=typeof e)continue;const t=e,o=t.function;i.push({id:String(t.id??t.toolCallId??""),name:String(o?.name??t.name??t.toolName??"tool"),arguments:"string"==typeof t.arguments?t.arguments:"string"==typeof o?.arguments?o.arguments:"",status:String(t.status??"completed"),output:"string"==typeof t.output?t.output:void 0})}0===i.length&&(i=void 0)}}if("user"===r&&p){p=stripBeeosUserEnvelope(p);const t=CANVAS_INTERACTION_RE.exec(p);if(t){const e=t[1];c={kind:"canvas_action"};const o={type:"canvas_action",actionName:e,surfaceId:t[2]},n=ACTION_CONTEXT_RE.exec(p);if(n)try{o.actionPayload=JSON.parse(n[1])}catch{}u=o,p=`Clicked "${e}"`}}if(!(p||l||i?.length||a?.length))continue;if(!c&&"user"===r&&e.provenance&&"object"==typeof e.provenance){const t=e.provenance;"string"==typeof t.kind&&(c={kind:t.kind},"string"==typeof t.sourceTool&&(c.sourceTool=t.sourceTool))}const f={role:r,content:p,reasoning:l,toolCalls:i,provenance:c,metadata:u,files:a};o.push(f),"assistant"===r&&(n=f)}o.filter(t=>t.toolCalls?.length),o.reduce((t,e)=>t+(e.toolCalls?.length??0),0),o.reduce((t,e)=>t+(e.toolCalls?.filter(t=>void 0!==t.output).length??0),0);return o}const BEEOS_FILE_URI_RE=/beeos-file:\/\/([a-zA-Z0-9_-]+)/g;export function extractBeeosFileUrisFromOutput(t){if(!t)return[];const e=[];for(const o of t.matchAll(BEEOS_FILE_URI_RE))e.push(o[0]);return[...new Set(e)]}const BEEOS_REF_RE=/<BEEOS_REF\s+([^/>]*)\s*\/>/g;function parseBeeosRefAttrs(t){const e=new Map,o=/(\w+)="([^"]*)"/g;for(const n of t.matchAll(o))e.set(n[1],n[2]);return e}function guessMimeFromName(t){if(!t)return;const e=t.split(".").pop()?.toLowerCase();return e?{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",pdf:"application/pdf",mp3:"audio/mpeg",wav:"audio/wav",mp4:"video/mp4",webm:"video/webm",txt:"text/plain",json:"application/json",csv:"text/csv"}[e]:void 0}export function splitBeeosRefReplayText(t){if(!t)return[];if(!t.includes("<BEEOS_REF "))return[{type:"text",text:t}];const e=[];let o=0;for(const n of t.matchAll(BEEOS_REF_RE)){const s=t.slice(o,n.index).trim();s&&e.push({type:"text",text:s});const r=parseBeeosRefAttrs(n[1]),i=r.get("id"),l=r.get("name"),a=r.get("status");i&&"download_failed"!==a?e.push({type:"resource_link",uri:`beeos-file://${i}`,fileName:l,mimeType:guessMimeFromName(l)??"application/octet-stream"}):i&&e.push({type:"resource_link",uri:`beeos-file://${i}`,fileName:l??"file",mimeType:guessMimeFromName(l)??"application/octet-stream"}),o=(n.index??0)+n[0].length}const n=t.slice(o).trim();return n&&e.push({type:"text",text:n}),e}const ATTACHMENT_MARKER_RE=/\[(image|file)\s+([^\]]*)\]/g;export function splitUserAttachmentMarkers(t){if(!t)return[];if(!t.includes("[image ")&&!t.includes("[file "))return[{type:"text",text:t}];const e=[];let o=0;for(const n of t.matchAll(ATTACHMENT_MARKER_RE)){const s=t.slice(o,n.index).trim();s&&e.push({type:"text",text:s});const r=n[1],i=n[2],l=new Map;for(const t of i.split(/\s+/)){const e=t.indexOf("=");e>0&&l.set(t.slice(0,e),t.slice(e+1))}e.push({type:r,mimeType:l.get("mime"),fileName:l.get("name")||("image"===r?"Image":"File")}),o=(n.index??0)+n[0].length}const n=t.slice(o).trim();return n&&e.push({type:"text",text:n}),e}export function splitUserReplayBlocks(t){return t?t.includes("<BEEOS_REF ")?splitBeeosRefReplayText(t):t.includes("[image ")||t.includes("[file ")?splitUserAttachmentMarkers(t):[{type:"text",text:t}]:[]}
1
+ import{stripTransportMetadata}from"../message-filter.js";function inferToolKindFromName(t){if(!t)return;const e=t.toLowerCase();return e.includes("read")?"read":e.includes("write")||e.includes("edit")?"edit":e.includes("delete")||e.includes("remove")?"delete":e.includes("move")||e.includes("rename")?"move":e.includes("search")||e.includes("find")?"search":e.includes("exec")||e.includes("run")||e.includes("bash")?"execute":e.includes("fetch")||e.includes("http")?"fetch":"other"}const BEEOS_USER_MSG_MARKER_RE=/(?:^|\n)\s*User Message From \w+:\s*\n/i,CANVAS_STATE_RE=/^\[Canvas State\][^\n]*/,SYSTEM_NOTE_RE=/^\[System Note\][^\n]*/,CANVAS_INTERACTION_RE=/^\[Canvas Interaction\] The user clicked "([^"]*)" on surface "([^"]*)"/,ACTION_CONTEXT_RE=/\nAction context: (.+?)(?:\n|$)/s;function stripBeeosUserEnvelope(t){const e=BEEOS_USER_MSG_MARKER_RE.exec(t);if(e){return t.slice(e.index+e[0].length).trim()}let o=t;return o=o.replace(CANVAS_STATE_RE,"").trimStart(),o=o.replace(SYSTEM_NOTE_RE,"").trimStart(),stripTransportMetadata(o).trim()}export async function loadHistory(t,e,o=50){if(!t?.isConnected)return[];try{const n=parseGatewayHistory(await t.chatHistory(e,o));if(n.length>0){n.filter(t=>"user"===t.role).length,n.filter(t=>"assistant"===t.role).length}return n}catch(t){return[]}}export function groupIntoTurns(t){const e=[];let o=null;const n=()=>{o&&(e.push(o),o=null)};for(const s of t){if("assistant"===s.role&&(s.reasoning||s.toolCalls?.length)){n(),o={role:"assistant",content:s.content,reasoning:s.reasoning,files:s.files},s.toolCalls?.length&&(o.toolCalls=s.toolCalls.map(t=>({id:t.id,callId:t.id,name:t.name,arguments:t.arguments??"",status:t.status??"completed",output:t.output,isError:"error"===t.status||"failed"===t.status,kind:inferToolKindFromName(t.name)})));continue}if("system"===s.role){n();const t={role:"system",content:s.content};s.metadata&&(t.metadata=s.metadata),e.push(t);continue}if("user"===s.role){n();const t={role:"user",content:s.content};s.provenance&&(t.provenance=s.provenance),s.metadata&&(t.metadata=s.metadata),e.push(t);continue}o||(o={role:"assistant",content:""});switch(s.updateType??"agent_message_chunk"){case"agent_thought_chunk":o.reasoning=(o.reasoning??"")+s.content;break;case"tool_call":o.toolCalls||(o.toolCalls=[]),o.toolCalls.push({id:s.toolCallId??`tc_${o.toolCalls.length}`,callId:s.toolCallId??`tc_${o.toolCalls.length}`,name:s.toolName??"tool",arguments:s.content||"",status:"completed",kind:inferToolKindFromName(s.toolName)});break;case"tool_call_update":{o.toolCalls||(o.toolCalls=[]);const t=s.toolCallId?o.toolCalls.find(t=>t.id===s.toolCallId):void 0;t?(t.output=s.content,t.status="completed"):o.toolCalls.push({id:s.toolCallId??`tc_${o.toolCalls.length}`,callId:s.toolCallId??`tc_${o.toolCalls.length}`,name:s.toolName??"tool",arguments:"",status:"completed",output:s.content,kind:inferToolKindFromName(s.toolName)});break}default:o.content+=s.content,s.files?.length&&(o.files=[...o.files??[],...s.files])}}return n(),e}const TOOL_CALL_BLOCK_TYPES=new Set(["toolCall","toolUse","functionCall","tool_use","tool_call","toolcall"]),TOOL_RESULT_BLOCK_TYPES=new Set(["tool_result","tool_result_error","toolResult"]);function extractBlockText(t){if("string"==typeof t)return t;if(!Array.isArray(t))return"";let e="";for(const o of t)"string"!=typeof o?o&&"object"==typeof o&&"string"==typeof o.text&&(e+=o.text):e+=o;return e}function parseGatewayHistory(t){if(!t||"object"!=typeof t)return[];const e=t.messages;if(!Array.isArray(e))return[];const o=[];let n=null;for(const t of e){if(!t||"object"!=typeof t)continue;const e=t,s="string"==typeof e.role?e.role:"assistant";if("toolResult"===s||"tool"===s){if(!n?.toolCalls?.length)continue;const t=String(e.toolCallId??e.tool_call_id??e.toolUseId??e.tool_use_id??""),o=extractBlockText(e.content);if(t){const s=n.toolCalls.find(e=>e.id===t);if(s)s.output=o,!0!==e.is_error&&!0!==e.isError||(s.status="error");else{const t=n.toolCalls.find(t=>void 0===t.output);t&&(t.output=o,!0!==e.is_error&&!0!==e.isError||(t.status="error"))}}else{const t=n.toolCalls.find(t=>void 0===t.output);t&&(t.output=o,!0!==e.is_error&&!0!==e.isError||(t.status="error"))}continue}if("user"===s&&n?.toolCalls?.length&&Array.isArray(e.content)){let t=!1,o=!1;for(const n of e.content){if(!n||"object"!=typeof n)continue;const e=n,s="string"==typeof e.type?e.type:"";TOOL_RESULT_BLOCK_TYPES.has(s)?t=!0:"text"===s&&"string"==typeof e.text&&e.text.trim()&&(o=!0)}if(t&&!o){for(const t of e.content){if(!t||"object"!=typeof t)continue;const e=t,o="string"==typeof e.type?e.type:"";if(!TOOL_RESULT_BLOCK_TYPES.has(o))continue;const s=String(e.toolUseId??e.tool_use_id??e.toolCallId??e.tool_call_id??""),i="string"==typeof e.output?e.output:extractBlockText(e.content);if(s){const t=n.toolCalls.find(t=>t.id===s);if(t)t.output=i,!0!==e.is_error&&!0!==e.isError||(t.status="error");else{const t=n.toolCalls.find(t=>void 0===t.output);t&&(t.output=i,!0!==e.is_error&&!0!==e.isError||(t.status="error"))}}else{const t=n.toolCalls.find(t=>void 0===t.output);t&&(t.output=i,!0!==e.is_error&&!0!==e.isError||(t.status="error"))}}continue}}const i="user"===s?"user":"assistant";let r,l,a,c,u,p="";if("string"==typeof e.content)p=e.content;else if(Array.isArray(e.content))for(const t of e.content){if("string"==typeof t){p+=t;continue}if(!t||"object"!=typeof t)continue;const e=t,o="string"==typeof e.type?e.type:"";if("text"===o&&"string"==typeof e.text)p+=e.text;else if("thinking"===o&&"string"==typeof e.thinking)l=(l??"")+e.thinking;else if("resource_link"===o&&"string"==typeof e.uri&&e.uri)a||(a=[]),a.push({uri:e.uri,..."string"==typeof e.name&&e.name?{name:e.name}:{},..."string"==typeof e.mimeType&&e.mimeType?{mimeType:e.mimeType}:{}});else if(TOOL_CALL_BLOCK_TYPES.has(o)){r||(r=[]);const t=e.arguments??e.input,o="string"==typeof t?t:null!=t?JSON.stringify(t):"";r.push({id:String(e.id??e.toolCallId??e.tool_call_id??""),name:String(e.name??"tool"),arguments:o,status:"completed"})}else if(TOOL_RESULT_BLOCK_TYPES.has(o)){const t=String(e.toolUseId??e.tool_use_id??e.toolCallId??e.tool_call_id??""),o="string"==typeof e.output?e.output:extractBlockText(e.content),n=!0===e.is_error||!0===e.isError;if(r){const e=t?r.find(e=>e.id===t):void 0;if(e)e.output=o,n&&(e.status="error");else{const t=r.find(t=>void 0===t.output);t&&(t.output=o,n&&(t.status="error"))}}}}if(l||("string"==typeof e.reasoning&&e.reasoning?l=e.reasoning:"string"==typeof e.thinking&&e.thinking&&(l=e.thinking)),!r?.length){const t=e.tool_calls??e.toolCalls;if(Array.isArray(t)){r=[];for(const e of t){if(!e||"object"!=typeof e)continue;const t=e,o=t.function;r.push({id:String(t.id??t.toolCallId??""),name:String(o?.name??t.name??t.toolName??"tool"),arguments:"string"==typeof t.arguments?t.arguments:"string"==typeof o?.arguments?o.arguments:"",status:String(t.status??"completed"),output:"string"==typeof t.output?t.output:void 0})}0===r.length&&(r=void 0)}}if("user"===i&&p){p=stripBeeosUserEnvelope(p);const t=CANVAS_INTERACTION_RE.exec(p);if(t){const e=t[1];c={kind:"canvas_action"};const o={type:"canvas_action",actionName:e,surfaceId:t[2]},n=ACTION_CONTEXT_RE.exec(p);if(n)try{o.actionPayload=JSON.parse(n[1])}catch{}u=o,p=`Clicked "${e}"`}}if(!(p||l||r?.length||a?.length))continue;if(!c&&"user"===i&&e.provenance&&"object"==typeof e.provenance){const t=e.provenance;"string"==typeof t.kind&&(c={kind:t.kind},"string"==typeof t.sourceTool&&(c.sourceTool=t.sourceTool))}const f={role:i,content:p,reasoning:l,toolCalls:r,provenance:c,metadata:u,files:a};o.push(f),"assistant"===i&&(n=f)}o.filter(t=>t.toolCalls?.length),o.reduce((t,e)=>t+(e.toolCalls?.length??0),0),o.reduce((t,e)=>t+(e.toolCalls?.filter(t=>void 0!==t.output).length??0),0);return o}const BEEOS_FILE_URI_RE=/beeos-file:\/\/([a-zA-Z0-9_-]+)/g;export function extractBeeosFileUrisFromOutput(t){if(!t)return[];const e=[];for(const o of t.matchAll(BEEOS_FILE_URI_RE))e.push(o[0]);return[...new Set(e)]}const BEEOS_REF_RE=/<BEEOS_REF\s+([^/>]*)\s*\/>/g;function parseBeeosRefAttrs(t){const e=new Map,o=/(\w+)="([^"]*)"/g;for(const n of t.matchAll(o))e.set(n[1],n[2]);return e}function guessMimeFromName(t){if(!t)return;const e=t.split(".").pop()?.toLowerCase();return e?{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",pdf:"application/pdf",mp3:"audio/mpeg",wav:"audio/wav",mp4:"video/mp4",webm:"video/webm",txt:"text/plain",json:"application/json",csv:"text/csv"}[e]:void 0}export function splitBeeosRefReplayText(t){if(!t)return[];if(!t.includes("<BEEOS_REF "))return[{type:"text",text:t}];const e=[];let o=0;for(const n of t.matchAll(BEEOS_REF_RE)){const s=t.slice(o,n.index).trim();s&&e.push({type:"text",text:s});const i=parseBeeosRefAttrs(n[1]),r=i.get("id"),l=i.get("name"),a=i.get("status");r&&"download_failed"!==a?e.push({type:"resource_link",uri:`beeos-file://${r}`,fileName:l,mimeType:guessMimeFromName(l)??"application/octet-stream"}):r&&e.push({type:"resource_link",uri:`beeos-file://${r}`,fileName:l??"file",mimeType:guessMimeFromName(l)??"application/octet-stream"}),o=(n.index??0)+n[0].length}const n=t.slice(o).trim();return n&&e.push({type:"text",text:n}),e}const ATTACHMENT_MARKER_RE=/\[(image|file)\s+([^\]]*)\]/g;export function splitUserAttachmentMarkers(t){if(!t)return[];if(!t.includes("[image ")&&!t.includes("[file "))return[{type:"text",text:t}];const e=[];let o=0;for(const n of t.matchAll(ATTACHMENT_MARKER_RE)){const s=t.slice(o,n.index).trim();s&&e.push({type:"text",text:s});const i=n[1],r=n[2],l=new Map;for(const t of r.split(/\s+/)){const e=t.indexOf("=");e>0&&l.set(t.slice(0,e),t.slice(e+1))}e.push({type:i,mimeType:l.get("mime"),fileName:l.get("name")||("image"===i?"Image":"File")}),o=(n.index??0)+n[0].length}const n=t.slice(o).trim();return n&&e.push({type:"text",text:n}),e}export function splitUserReplayBlocks(t){return t?t.includes("<BEEOS_REF ")?splitBeeosRefReplayText(t):t.includes("[image ")||t.includes("[file ")?splitUserAttachmentMarkers(t):[{type:"text",text:t}]:[]}
@@ -1 +1 @@
1
- import{readUploadToolResult}from"../upload/result-cache.js";import{BEEOS_UPLOAD_TOOL_NAME}from"../upload/tool.js";import{asTrimmedNonEmptyString as asStr,isPlainRecord as isRecord}from"../utils/text.js";const strategies=[{name:"beeos_upload_file_cache",canHandle:t=>t.toolName===BEEOS_UPLOAD_TOOL_NAME,resolve:strategyUploadCache},{name:"passthrough_gateway_result",canHandle:()=>!0,resolve:async t=>strategyPassthrough(t)}];export function registerToolResultStrategy(t,e){if(e?.prepend)strategies.unshift(t);else{const e=Math.max(0,strategies.length-1);strategies.splice(e,0,t)}}export function getToolResultStrategies(){return strategies}export async function resolveToolResultBlocks(t){for(const e of strategies)if(e.canHandle(t))try{const n=await e.resolve(t);if(n&&n.length>0)return n}catch{}return[]}async function strategyUploadCache(t){if(t.toolName!==BEEOS_UPLOAD_TOOL_NAME)return null;let e;try{e=await readUploadToolResult(t.toolCallId)}catch{return null}if(!e)return null;const n=pickUploadPayload(e);return buildToolResultSessionContentBlocks(t.toolName,n??e)}function strategyPassthrough(t){if(t.rawResult&&isRecord(t.rawResult)){const e=buildToolResultSessionContentBlocks(t.toolName,t.rawResult);if(e.length>0)return e}const e=t.rawText?.trim();if(!e)return[];try{const n=JSON.parse(e);if(isRecord(n)){const e=buildToolResultSessionContentBlocks(t.toolName,n);if(e.length>0)return e}}catch{}return[{type:"content",content:{type:"text",text:e}}]}function isUploadPayload(t){return!!isRecord(t)&&(!0===t.ok||!1===t.ok||"files"in t||Array.isArray(t.content)&&t.content.length>0)}function pickUploadPayload(t){if(isRecord(t)){for(const e of["output","result","details"]){const n=t[e];if(isUploadPayload(n))return n;if(isRecord(n))for(const t of["output","result","details"])if(isUploadPayload(n[t]))return n[t]}return Array.isArray(t.content)&&t.content.length>0||isUploadPayload(t)?t:void 0}}export function buildToolResultSessionContentBlocks(t,e){const n=[];if(!isRecord(e))return"string"==typeof e&&e.trim()&&n.push({type:"content",content:{type:"text",text:e.trim()}}),n;const o=e.content??e.result;if(Array.isArray(o)){for(const t of o){const e=toContentBlock(t);e&&n.push(e)}if(n.length>0)return n}if(Array.isArray(e.files)){for(const t of e.files){if(!isRecord(t))continue;const e=asStr(t.fileId)??asStr(t.file_id),o=e?`beeos-file://${e}`:asStr(t.uri)??asStr(t.downloadUrl)??asStr(t.url);if(!o)continue;const r=asStr(t.name)??asStr(t.fileName)??asStr(t.file_name),s=asStr(t.mimeType)??asStr(t.mime_type);n.push({type:"content",content:{type:"resource_link",uri:o,...r?{name:r}:{},...s?{mimeType:s}:{}}})}if(n.length>0)return n}const r=asStr(e.output)??asStr(e.text)??asStr(e.result);return r&&n.push({type:"content",content:{type:"text",text:r}}),n}function toContentBlock(t){if(!isRecord(t))return"string"==typeof t&&t.trim()?{type:"content",content:{type:"text",text:t.trim()}}:null;const e=asStr(t.type);if(!e)return null;if("text"===e){const e=asStr(t.text);return e?{type:"content",content:{type:"text",text:e}}:null}if("resource_link"===e){const e=asStr(t.uri);return e?{type:"content",content:{type:"resource_link",uri:e,...t.name?{name:String(t.name)}:{},...t.mimeType?{mimeType:String(t.mimeType)}:{},...t.title?{title:String(t.title)}:{}}}:null}if("image"===e){const e=asStr(t.data),n=asStr(t.uri);return e||n?{type:"content",content:{type:"image",...e?{data:e}:{},...n?{uri:n}:{},...t.mimeType?{mimeType:String(t.mimeType)}:{}}}:null}return null}
1
+ import{readUploadToolResult}from"../upload/result-cache.js";import{BEEOS_UPLOAD_TOOL_NAME}from"../upload/tool.js";import{asTrimmedNonEmptyString as asStr,isPlainRecord as isRecord}from"../utils/text.js";const strategies=[{name:"beeos_upload_file_cache",canHandle:t=>t.toolName===BEEOS_UPLOAD_TOOL_NAME,resolve:strategyUploadCache},{name:"passthrough_gateway_result",canHandle:()=>!0,resolve:async t=>strategyPassthrough(t)}];export function registerToolResultStrategy(t,e){if(e?.prepend)strategies.unshift(t);else{const e=Math.max(0,strategies.length-1);strategies.splice(e,0,t)}}export function getToolResultStrategies(){return strategies}export async function resolveToolResultBlocks(t){for(const e of strategies)if(e.canHandle(t))try{const o=await e.resolve(t);if(o&&o.length>0)return o}catch{}return[]}async function strategyUploadCache(t){if(t.toolName!==BEEOS_UPLOAD_TOOL_NAME)return null;let e;try{e=await readUploadToolResult(t.toolCallId)}catch{return null}if(!e)return null;const o=pickUploadPayload(e);return buildToolResultSessionContentBlocks(t.toolName,o??e)}function strategyPassthrough(t){if(t.rawResult&&isRecord(t.rawResult)){const e=buildToolResultSessionContentBlocks(t.toolName,t.rawResult);if(e.length>0)return e}const e=t.rawText?.trim();if(!e)return[];try{const o=JSON.parse(e);if(isRecord(o)){const e=buildToolResultSessionContentBlocks(t.toolName,o);if(e.length>0)return e}}catch{}return[{type:"content",content:{type:"text",text:e}}]}function isUploadPayload(t){return!!isRecord(t)&&(!0===t.ok||!1===t.ok||"files"in t||Array.isArray(t.content)&&t.content.length>0)}function pickUploadPayload(t){if(isRecord(t)){for(const e of["output","result","details"]){const o=t[e];if(isUploadPayload(o))return o;if(isRecord(o))for(const t of["output","result","details"])if(isUploadPayload(o[t]))return o[t]}return Array.isArray(t.content)&&t.content.length>0||isUploadPayload(t)?t:void 0}}export function buildToolResultSessionContentBlocks(t,e){const o=[];if(!isRecord(e))return"string"==typeof e&&e.trim()&&o.push({type:"content",content:{type:"text",text:e.trim()}}),o;const r=e.content??e.result;if(Array.isArray(r)){for(const t of r){const e=toContentBlock(t);e&&o.push(e)}if(o.length>0)return o}if(Array.isArray(e.files)){for(const t of e.files){if(!isRecord(t))continue;const e=asStr(t.fileId)??asStr(t.file_id),r=e?`beeos-file://${e}`:asStr(t.uri)??asStr(t.downloadUrl)??asStr(t.url);if(!r)continue;const n=asStr(t.name)??asStr(t.fileName)??asStr(t.file_name),s=asStr(t.mimeType)??asStr(t.mime_type);o.push({type:"content",content:{type:"resource_link",uri:r,...n?{name:n}:{},...s?{mimeType:s}:{}}})}if(o.length>0)return o}const n=asStr(e.output)??asStr(e.text)??asStr(e.result)??asStr(e.message)??asStr(e.error);return n&&o.push({type:"content",content:{type:"text",text:n}}),o}function toContentBlock(t){if(!isRecord(t))return"string"==typeof t&&t.trim()?{type:"content",content:{type:"text",text:t.trim()}}:null;const e=asStr(t.type);if(!e)return null;if("text"===e){const e=asStr(t.text);return e?{type:"content",content:{type:"text",text:e}}:null}if("resource_link"===e){const e=asStr(t.uri);return e?{type:"content",content:{type:"resource_link",uri:e,...t.name?{name:String(t.name)}:{},...t.mimeType?{mimeType:String(t.mimeType)}:{},...t.title?{title:String(t.title)}:{}}}:null}if("image"===e){const e=asStr(t.data),o=asStr(t.uri);return e||o?{type:"content",content:{type:"image",...e?{data:e}:{},...o?{uri:o}:{},...t.mimeType?{mimeType:String(t.mimeType)}:{}}}:null}return null}
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{MessageSDK}from"./messaging/message-sdk.js";import{registerA2ATools}from"./a2a/tools.js";import{createA2ATaskHandler}from"./a2a/task-handler.js";import{createA2AResultHandler}from"./a2a/result-handler.js";import{createA2ATaskCancelHandler}from"./a2a/cancel-handler.js";import{TaskHandleRegistry}from"./a2a/task-handle.js";import{pollPendingTasks}from"./a2a/poll-pending.js";import{BEEOS_UPLOAD_TOOL_NAME,BEEOS_UPLOAD_TOOL_SCHEMA,BEEOS_UPLOAD_TOOL_DESCRIPTION,executeUpload}from"./upload/tool.js";import{writeUploadToolResult,cleanupUploadToolResultCache}from"./upload/result-cache.js";import{handleFileStage}from"./upload/file-stage.js";import{ACPServer}from"./acp-server.js";import{createObsEventWriter}from"./observability.js";import{resolvePluginVersion}from"./plugin-version.js";import{createTerminalWebSocketService}from"./terminal/websocket.js";import{TerminalManager}from"./terminal/session-manager.js";import{loadAgentKeyPair,agentFetch}from"./agent-auth.js";import{readDiscoveryCache,writeDiscoveryCache}from"./discovery-cache.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{CanvasStateManager}from"./canvas/state.js";import{CanvasRelayClient}from"./canvas/relay-client.js";import{RENDER_UI_TOOL_NAME,RENDER_UI_TOOL_DESCRIPTION,RENDER_UI_TOOL_SCHEMA,CLOSE_UI_TOOL_NAME,CLOSE_UI_TOOL_DESCRIPTION,CLOSE_UI_TOOL_SCHEMA,createRenderUiHandler,createCloseUiHandler,CANVAS_SYSTEM_PROMPT}from"./canvas/tools.js";import{FormSchemaRegistry}from"./surface/form-schema-registry.js";import{CRON_CREATE_SCHEMA,CRON_UPDATE_SCHEMA}from"./surface/cron-schemas.js";import{RENDER_FORM_TOOL_NAME,RENDER_FORM_TOOL_SCHEMA,buildRenderFormDescription,createRenderFormHandler}from"./surface/render-form-tool.js";import{ResourceDescriptorRegistry}from"./surface/resource-descriptor-registry.js";import{CRON_RESOURCE}from"./surface/cron-resource.js";import{generateActionTools,RENDER_RESOURCE_TOOL_NAME,RENDER_RESOURCE_TOOL_SCHEMA,buildRenderResourceDescription,createRenderResourceHandler}from"./surface/action-tool-generator.js";import{buildThirdPartyChannelSet,isThirdPartyChannel}from"./channel-detection.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null;function initializePlugin(e){let o,a;try{o=e.runtime?.config?.loadConfig?.()}catch{}try{a=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:o})}catch(o){return e.logger.error?.("[beeos-claw] Failed to resolve config:",o),null}const r=a.config,t=buildThirdPartyChannelSet(o);t.size>0&&e.logger.info?.(`[beeos-claw] Third-party channels detected: ${[...t].join(", ")}`);const s=r.log.enabled?createObsEventWriter({dir:r.log.dir,redaction:{gatewayToken:r.gateway.token},logger:e.logger}):()=>{};s({component:"plugin",domain:"lifecycle",name:"lifecycle.startup",severity:"info",payload:{pluginVersion:PLUGIN_VERSION,pid:process.pid,nodeVersion:process.version,cwd:process.cwd()}}),s({component:"plugin",domain:"config",name:"config.resolved",severity:"info",payload:{config:r,sources:a.sources,openclawConfigPath:a.openclawConfigPath}});for(const o of a.validation)s({component:"plugin",domain:"config",name:"config.validation",severity:o.severity,summary:o.message,error:{code:o.code,message:o.message,nextSteps:o.nextSteps}}),"error"===o.severity?e.logger.error?.(`[beeos-claw] ${o.message}`):e.logger.warn?.(`[beeos-claw] ${o.message}`);if(a.validation.some(e=>"error"===e.severity)&&e.logger.error?.("[beeos-claw] Plugin has configuration errors — some features may not work."),r.keyFile)try{agentKeys=loadAgentKeyPair(r.keyFile);const o=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${o})`)}catch(o){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${o}`),e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no key pair — signed requests disabled)`)}else e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no keyFile configured)`);return{cfg:r,writeObs:s,thirdPartyChannels:t,openclawConfig:o}}function registerUploadTools(e,o,a){const{cfg:r,writeObs:t}=o;cleanupUploadToolResultCache().catch(o=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(o)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(o,s){e.logger.debug?.(`[beeos_upload_file] toolCallId=${o} params=${JSON.stringify(s)}`);const n=await executeUpload(s,r,agentKeys);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${o} ok=${n.ok}`),t({component:"upload-tool",domain:"tool",name:n.ok?"upload.success":"upload.failure",severity:n.ok?"info":"warn",payload:{toolCallId:o,ok:n.ok,fileCount:n.files?.length??0,error:n.error}}),n.ok&&n.content&&a.current&&a.current.pushToolResultContent(o,BEEOS_UPLOAD_TOOL_NAME,n.content),(async a=>{try{await writeUploadToolResult(o,BEEOS_UPLOAD_TOOL_NAME,a),await cleanupUploadToolResultCache()}catch(a){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${o}: ${String(a)}`)}return a})(n)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,a){await handleFileStage(e,a,o.cfg)}})}function registerServices(e,o,a){const{cfg:r,writeObs:t,thirdPartyChannels:s}=o,n=agentKeys?(e,o)=>agentFetch(e,agentKeys,o):void 0,i=agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):void 0,l=new ACPServer(r,e.logger,t,n,i,s);a.current=l,e.registerService({id:"beeos-acp",async start(){if(!r.bridge.url&&r.agentGatewayUrl&&n){const o=await discoverBridgeFromGW(r.agentGatewayUrl,n);if(o)r.bridge.url=o,writeDiscoveryCache({bridge:{url:o,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${o}`);else{const o=readDiscoveryCache();o?.bridge?.url?(r.bridge.url=o.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${o.bridge.url}`)):e.logger.debug?.("[beeos-claw] Bridge URL not available (no GW, no cache) — bridge disabled")}}await l.start(r.bridge.url||void 0),e.logger.info?.("[beeos-claw] ACP Server started")},async stop(){await l.stop(),e.logger.info?.("[beeos-claw] ACP Server stopped")}});const g=new TaskHandleRegistry;let c=null;const{enabled:d,url:p,principalId:u,a2aEnabled:f}=r.messageBus;let y;if(r.agentGatewayUrl&&agentKeys){const e=r.agentGatewayUrl,o=agentKeys;y=async a=>{const r=await agentFetch(`${e}/api/v1/messaging/token`,o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({principal_id:a})});if(!r.ok){const e=await r.text().catch(()=>"");throw new Error(`Gateway messaging token returned ${r.status}: ${e}`)}return r.json()}}if(d&&(y||r.messageBus.serviceUrl)){c=new MessageSDK({serviceUrl:y?void 0:r.messageBus.serviceUrl,centrifugoUrl:p},e.logger);const o=u;e.registerService({id:"beeos-message-bus",async start(){let a=o;if(a||(a=process.env.BEEOS_PRINCIPAL_ID||""),!a&&l)try{await l.waitForAgentMapping(),a=l.getDefaultPlatformAgentId()||""}catch(o){e.logger.warn?.("[beeos-claw] Failed to resolve principalId from agent sync:",o)}a?(await c.connect(a,{centrifugoUrl:p,tokenProvider:y}),e.logger.info?.("[beeos-claw] Message SDK connected (principalId=%s, via=%s)",a,y?"gateway":"direct")):e.logger.warn?.("[beeos-claw] No principalId resolved, message bus disabled")},async stop(){await(c?.disconnect()),e.logger.info?.("[beeos-claw] Message SDK disconnected")}});const a=(e,o,a,r)=>l.agentSend(e,o,a,r),t=(e,o,a)=>l.trackA2ARun(e,o,a),s=e=>l.emitA2ASystemNotification(e),n=r.agentGatewayUrl?{agentGatewayUrl:r.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger}:void 0;c.onMessage("a2a_task",createA2ATaskHandler(a,n,t,s)),c.onMessage("a2a_result",createA2AResultHandler(a,g,s)),c.onMessage("a2a_task_cancel",createA2ATaskCancelHandler(a,e=>l.cancelA2ARunByTaskId(e))),n&&e.registerService({id:"beeos-a2a-poll",async start(){await l.waitForAgentMapping();const o=await pollPendingTasks(n,a,t);o>0&&e.logger.info?.("[beeos-claw] Dispatched %d pending A2A tasks on startup",o)}})}else d?e.logger.warn?.("[beeos-claw] Message bus enabled but MESSAGE_SERVICE_URL not set — disabled"):e.logger.info?.("[beeos-claw] Message bus disabled (enabled=%s)",d);if(!1!==f&&r.agentGatewayUrl){const o=e=>l.getGatewaySessionKeyForToolCall(e),a=e=>l.getMentionsForToolCall(e),t=(e,o)=>{l.broadcastNotification(e,o)};registerA2ATools(e,{agentGatewayUrl:r.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},g,o,a,t),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(r.canvasEnabled){const o=new CanvasStateManager;if(r.agentGatewayUrl&&agentKeys){let a;o.setLogger(e.logger);{const e=r.agentGatewayUrl,o=agentKeys;a=async()=>{const a=await agentFetch(`${e}/api/v1/canvas/token`,o,{method:"POST"});if(!a.ok){const e=await a.text().catch(()=>"");throw new Error(`Canvas token returned ${a.status}: ${e}`)}return a.json()}}e.registerService({id:"beeos-canvas-relay",async start(){try{await l.waitForAgentMapping();const t=l.getDefaultPlatformAgent();if(!t?.id)return void e.logger.info?.("[beeos-claw] Canvas Relay skipped: no agentId after sync");const s=new CanvasRelayClient({canvasRelayUrl:r.canvasRelayUrl,agentKeys:agentKeys,agentId:t.id,displayName:t.displayName,avatarUrl:t.avatarUrl,tokenProvider:a,onReconnect:()=>l.onRelayReconnected(),logger:e.logger});s.connect(),o.setRelayClient(s),o.setAgentId(t.id),l.wireRelayActionHandler(),l.flushPendingCanvasLinks(),e.logger.info?.(`[beeos-claw] Canvas Relay multiplexed client connected (agentId=${t.id}, name=${t.name??"none"})`)}catch(o){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${o}`)}},stop(){o.getRelayClient()?.disconnect()}}),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const a={stateManager:o,logger:e.logger,writeObs:e=>t(e),isCanvasActive:()=>l.isCanvasActive(),getActiveSessionId:e=>l.getSessionIdForToolCall(e),getPlatformAgentId:e=>l.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>l.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>l.waitForAgentMapping()},n=new FormSchemaRegistry;n.register(CRON_CREATE_SCHEMA),n.register(CRON_UPDATE_SCHEMA);const i=new ResourceDescriptorRegistry;i.register(CRON_RESOURCE);const g=e=>async(o,a)=>(l.preMapToolCallSession(o),e(o,a));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:g(createRenderUiHandler(a))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:g(createCloseUiHandler(a))}),l.setCanvasState(o,{schemaRegistry:n,descriptorRegistry:i});const c=l.getFormBuilder();c&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(n),parameters:RENDER_FORM_TOOL_SCHEMA,execute:g(createRenderFormHandler({registry:n,builder:c,logger:e.logger}))});const d=l.getResourceRenderer();if(d){generateActionTools(i,e,async(e,o)=>l.gatewayRequest(e,o),d,e.logger),e.registerTool({name:RENDER_RESOURCE_TOOL_NAME,description:buildRenderResourceDescription(i),parameters:RENDER_RESOURCE_TOOL_SCHEMA,execute:g(createRenderResourceHandler(i,d,e.logger))})}const p=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME,RENDER_RESOURCE_TOOL_NAME]),u=256,f=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,o)=>{if(o?.sessionKey){if(f.size>=u&&!f.has(o.sessionKey)){const e=f.keys().next().value;void 0!==e&&f.delete(e)}f.set(o.sessionKey,o.channelId)}if(isThirdPartyChannel(s,{sessionKey:o?.sessionKey,channelId:o?.channelId}))return;return(o?.sessionKey?l.isCanvasActiveForGatewayKey(o.sessionKey):l.isCanvasActive())?{appendSystemContext:CANVAS_SYSTEM_PROMPT}:{appendSystemContext:"Canvas is closed by user. Do NOT use render_ui, close_ui, render_form, or render_resource. Respond with plain text only."}}),e.logger.debug?.("[beeos-claw] Canvas system prompt hook registered: before_prompt_build")}catch{e.logger.debug?.("[beeos-claw] before_prompt_build hook not supported")}try{e.on("before_tool_call",(e,o)=>{if(o?.sessionKey&&l.setPendingToolCallSessionKey(o.sessionKey),!p.has(e.toolName))return;if(isThirdPartyChannel(s,{sessionKey:o?.sessionKey}))return{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."};return(o?.sessionKey?l.isCanvasActiveForGatewayKey(o.sessionKey):l.isCanvasActive())?void 0:{block:!0,blockReason:"Canvas is closed by user. Respond with plain text instead."}}),e.logger.debug?.("[beeos-claw] Canvas tool guard registered: before_tool_call")}catch{e.logger.debug?.("[beeos-claw] before_tool_call hook not supported")}}e.logger.info?.("[beeos-claw] Canvas tools registered (render_ui, close_ui, render_form, render_resource + auto-action tools)")}else e.logger.info?.("[beeos-claw] Canvas disabled by config (canvasEnabled=false)");const m=new TerminalManager,_=createTerminalWebSocketService({config:{port:r.terminalWsPort,token:agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):"",allowedOrigins:r.terminalWsAllowedOrigins},terminalMgr:m,logger:e.logger,writeObs:t});if(r.bridge.shell.enabled?e.registerService({id:"beeos-terminal-ws",start(){_.start(),e.logger.info?.(`[beeos-claw] Terminal WS started on port ${r.terminalWsPort}`)},stop(){_.stop(),m.destroy(),e.logger.info?.("[beeos-claw] Terminal WS stopped")}}):e.logger.info?.("[beeos-claw] Terminal/shell disabled by config (bridge.shell.enabled=false)"),o.openclawConfig){const a=mirrorOpenClawConfig(o.openclawConfig);a.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${a.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${a.reason}`)}}const plugin={id:"beeos-claw",name:"beeos-claw",description:"BeeOS platform bridge plugin for OpenClaw. Provides file upload/download, ACP bridge, and observability.",version:PLUGIN_VERSION,register(e){if(e.registrationMode&&"full"!==e.registrationMode)return void e.logger.debug?.(`[beeos-claw] Skipping registration (mode=${e.registrationMode})`);const o=initializePlugin(e);if(!o)return;const a={current:null};registerUploadTools(e,o,a),registerServices(e,o,a),e.logger.info?.("[beeos-claw] Plugin registered successfully")}};export default plugin;
1
+ import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{MessageSDK}from"./messaging/message-sdk.js";import{registerA2ATools}from"./a2a/tools.js";import{createA2ATaskHandler}from"./a2a/task-handler.js";import{createA2AResultHandler}from"./a2a/result-handler.js";import{createA2ATaskCancelHandler}from"./a2a/cancel-handler.js";import{TaskHandleRegistry}from"./a2a/task-handle.js";import{pollPendingTasks}from"./a2a/poll-pending.js";import{BEEOS_UPLOAD_TOOL_NAME,BEEOS_UPLOAD_TOOL_SCHEMA,BEEOS_UPLOAD_TOOL_DESCRIPTION,executeUpload}from"./upload/tool.js";import{writeUploadToolResult,cleanupUploadToolResultCache}from"./upload/result-cache.js";import{handleFileStage}from"./upload/file-stage.js";import{ACPServer}from"./acp-server.js";import{createObsEventWriter}from"./observability.js";import{resolvePluginVersion}from"./plugin-version.js";import{createTerminalWebSocketService}from"./terminal/websocket.js";import{TerminalManager}from"./terminal/session-manager.js";import{loadAgentKeyPair,agentFetch}from"./agent-auth.js";import{readDiscoveryCache,writeDiscoveryCache}from"./discovery-cache.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{CanvasStateManager}from"./canvas/state.js";import{CanvasRelayClient}from"./canvas/relay-client.js";import{RENDER_UI_TOOL_NAME,RENDER_UI_TOOL_DESCRIPTION,RENDER_UI_TOOL_SCHEMA,CLOSE_UI_TOOL_NAME,CLOSE_UI_TOOL_DESCRIPTION,CLOSE_UI_TOOL_SCHEMA,createRenderUiHandler,createCloseUiHandler,CANVAS_SYSTEM_PROMPT}from"./canvas/tools.js";import{FormSchemaRegistry}from"./surface/form-schema-registry.js";import{CRON_CREATE_SCHEMA,CRON_UPDATE_SCHEMA}from"./surface/cron-schemas.js";import{RENDER_FORM_TOOL_NAME,RENDER_FORM_TOOL_SCHEMA,buildRenderFormDescription,createRenderFormHandler}from"./surface/render-form-tool.js";import{ResourceDescriptorRegistry}from"./surface/resource-descriptor-registry.js";import{CRON_RESOURCE}from"./surface/cron-resource.js";import{generateActionTools}from"./surface/action-tool-generator.js";import{buildThirdPartyChannelSet,isThirdPartyChannel}from"./channel-detection.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null;function initializePlugin(e){let o,a;try{o=e.runtime?.config?.loadConfig?.()}catch{}try{a=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:o})}catch(o){return e.logger.error?.("[beeos-claw] Failed to resolve config:",o),null}const r=a.config,t=buildThirdPartyChannelSet(o);t.size>0&&e.logger.info?.(`[beeos-claw] Third-party channels detected: ${[...t].join(", ")}`);const s=r.log.enabled?createObsEventWriter({dir:r.log.dir,redaction:{gatewayToken:r.gateway.token},logger:e.logger}):()=>{};s({component:"plugin",domain:"lifecycle",name:"lifecycle.startup",severity:"info",payload:{pluginVersion:PLUGIN_VERSION,pid:process.pid,nodeVersion:process.version,cwd:process.cwd()}}),s({component:"plugin",domain:"config",name:"config.resolved",severity:"info",payload:{config:r,sources:a.sources,openclawConfigPath:a.openclawConfigPath}});for(const o of a.validation)s({component:"plugin",domain:"config",name:"config.validation",severity:o.severity,summary:o.message,error:{code:o.code,message:o.message,nextSteps:o.nextSteps}}),"error"===o.severity?e.logger.error?.(`[beeos-claw] ${o.message}`):e.logger.warn?.(`[beeos-claw] ${o.message}`);if(a.validation.some(e=>"error"===e.severity)&&e.logger.error?.("[beeos-claw] Plugin has configuration errors — some features may not work."),r.keyFile)try{agentKeys=loadAgentKeyPair(r.keyFile);const o=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${o})`)}catch(o){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${o}`),e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no key pair — signed requests disabled)`)}else e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no keyFile configured)`);return{cfg:r,writeObs:s,thirdPartyChannels:t,openclawConfig:o}}function registerUploadTools(e,o,a){const{cfg:r,writeObs:t}=o;cleanupUploadToolResultCache().catch(o=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(o)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(o,s){e.logger.debug?.(`[beeos_upload_file] toolCallId=${o} params=${JSON.stringify(s)}`);const n=await executeUpload(s,r,agentKeys);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${o} ok=${n.ok}`),t({component:"upload-tool",domain:"tool",name:n.ok?"upload.success":"upload.failure",severity:n.ok?"info":"warn",payload:{toolCallId:o,ok:n.ok,fileCount:n.files?.length??0,error:n.error}}),n.ok&&n.content&&a.current&&a.current.pushToolResultContent(o,BEEOS_UPLOAD_TOOL_NAME,n.content),(async a=>{try{await writeUploadToolResult(o,BEEOS_UPLOAD_TOOL_NAME,a),await cleanupUploadToolResultCache()}catch(a){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${o}: ${String(a)}`)}return a})(n)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,a){await handleFileStage(e,a,o.cfg)}})}function registerServices(e,o,a){const{cfg:r,writeObs:t,thirdPartyChannels:s}=o,n=agentKeys?(e,o)=>agentFetch(e,agentKeys,o):void 0,i=agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):void 0,l=new ACPServer(r,e.logger,t,n,i,s);a.current=l,e.registerService({id:"beeos-acp",async start(){if(!r.bridge.url&&r.agentGatewayUrl&&n){const o=await discoverBridgeFromGW(r.agentGatewayUrl,n);if(o)r.bridge.url=o,writeDiscoveryCache({bridge:{url:o,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${o}`);else{const o=readDiscoveryCache();o?.bridge?.url?(r.bridge.url=o.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${o.bridge.url}`)):e.logger.debug?.("[beeos-claw] Bridge URL not available (no GW, no cache) — bridge disabled")}}await l.start(r.bridge.url||void 0),e.logger.info?.("[beeos-claw] ACP Server started")},async stop(){await l.stop(),e.logger.info?.("[beeos-claw] ACP Server stopped")}});const g=new TaskHandleRegistry;let c=null;const{enabled:d,url:p,principalId:f,a2aEnabled:u}=r.messageBus;let y;if(r.agentGatewayUrl&&agentKeys){const e=r.agentGatewayUrl,o=agentKeys;y=async a=>{const r=await agentFetch(`${e}/api/v1/messaging/token`,o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({principal_id:a})});if(!r.ok){const e=await r.text().catch(()=>"");throw new Error(`Gateway messaging token returned ${r.status}: ${e}`)}return r.json()}}if(d&&(y||r.messageBus.serviceUrl)){c=new MessageSDK({serviceUrl:y?void 0:r.messageBus.serviceUrl,centrifugoUrl:p},e.logger);const o=f;e.registerService({id:"beeos-message-bus",async start(){let a=o;if(a||(a=process.env.BEEOS_PRINCIPAL_ID||""),!a&&l)try{await l.waitForAgentMapping(),a=l.getDefaultPlatformAgentId()||""}catch(o){e.logger.warn?.("[beeos-claw] Failed to resolve principalId from agent sync:",o)}a?(await c.connect(a,{centrifugoUrl:p,tokenProvider:y}),e.logger.info?.("[beeos-claw] Message SDK connected (principalId=%s, via=%s)",a,y?"gateway":"direct")):e.logger.warn?.("[beeos-claw] No principalId resolved, message bus disabled")},async stop(){await(c?.disconnect()),e.logger.info?.("[beeos-claw] Message SDK disconnected")}});const a=(e,o,a,r)=>l.agentSend(e,o,a,r),t=(e,o,a)=>l.trackA2ARun(e,o,a),s=e=>l.emitA2ASystemNotification(e),n=r.agentGatewayUrl?{agentGatewayUrl:r.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger}:void 0;c.onMessage("a2a_task",createA2ATaskHandler(a,n,t,s)),c.onMessage("a2a_result",createA2AResultHandler(a,g,s)),c.onMessage("a2a_task_cancel",createA2ATaskCancelHandler(a,e=>l.cancelA2ARunByTaskId(e))),n&&e.registerService({id:"beeos-a2a-poll",async start(){await l.waitForAgentMapping();const o=await pollPendingTasks(n,a,t);o>0&&e.logger.info?.("[beeos-claw] Dispatched %d pending A2A tasks on startup",o)}})}else d?e.logger.warn?.("[beeos-claw] Message bus enabled but MESSAGE_SERVICE_URL not set — disabled"):e.logger.info?.("[beeos-claw] Message bus disabled (enabled=%s)",d);if(!1!==u&&r.agentGatewayUrl){const o=e=>l.getGatewaySessionKeyForToolCall(e),a=e=>l.getMentionsForToolCall(e),t=(e,o)=>{l.broadcastNotification(e,o)};registerA2ATools(e,{agentGatewayUrl:r.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},g,o,a,t),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(r.canvasEnabled){const o=new CanvasStateManager;if(r.agentGatewayUrl&&agentKeys){let a;o.setLogger(e.logger);{const e=r.agentGatewayUrl,o=agentKeys;a=async()=>{const a=await agentFetch(`${e}/api/v1/canvas/token`,o,{method:"POST"});if(!a.ok){const e=await a.text().catch(()=>"");throw new Error(`Canvas token returned ${a.status}: ${e}`)}return a.json()}}e.registerService({id:"beeos-canvas-relay",async start(){try{await l.waitForAgentMapping();const t=l.getDefaultPlatformAgent();if(!t?.id)return void e.logger.info?.("[beeos-claw] Canvas Relay skipped: no agentId after sync");const s=new CanvasRelayClient({canvasRelayUrl:r.canvasRelayUrl,agentKeys:agentKeys,agentId:t.id,displayName:t.displayName,avatarUrl:t.avatarUrl,tokenProvider:a,onReconnect:()=>l.onRelayReconnected(),logger:e.logger});s.connect(),o.setRelayClient(s),o.setAgentId(t.id),l.wireRelayActionHandler(),l.flushPendingCanvasLinks(),e.logger.info?.(`[beeos-claw] Canvas Relay multiplexed client connected (agentId=${t.id}, name=${t.name??"none"})`)}catch(o){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${o}`)}},stop(){o.getRelayClient()?.disconnect()}}),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const a={stateManager:o,logger:e.logger,writeObs:e=>t(e),isCanvasActive:()=>l.isCanvasActive(),getActiveSessionId:e=>l.getSessionIdForToolCall(e),getPlatformAgentId:e=>l.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>l.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>l.waitForAgentMapping()},n=new FormSchemaRegistry;n.register(CRON_CREATE_SCHEMA),n.register(CRON_UPDATE_SCHEMA);const i=new ResourceDescriptorRegistry;i.register(CRON_RESOURCE);const g=e=>async(o,a)=>(l.preMapToolCallSession(o),e(o,a));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:g(createRenderUiHandler(a))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:g(createCloseUiHandler(a))}),l.setCanvasState(o,{schemaRegistry:n,descriptorRegistry:i});const c=l.getFormBuilder();c&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(n),parameters:RENDER_FORM_TOOL_SCHEMA,execute:g(createRenderFormHandler({registry:n,builder:c,logger:e.logger}))});const d=l.getResourceRenderer();if(d){generateActionTools(i,e,async(e,o)=>l.gatewayRequest(e,o),d,e.logger)}const p=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),f=256,u=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,o)=>{if(o?.sessionKey){if(u.size>=f&&!u.has(o.sessionKey)){const e=u.keys().next().value;void 0!==e&&u.delete(e)}u.set(o.sessionKey,o.channelId)}if(isThirdPartyChannel(s,{sessionKey:o?.sessionKey,channelId:o?.channelId}))return;return(o?.sessionKey?l.isCanvasActiveForGatewayKey(o.sessionKey):l.isCanvasActive())?{appendSystemContext:CANVAS_SYSTEM_PROMPT}:{appendSystemContext:"Canvas is closed by user. Do NOT use render_ui, close_ui, or render_form. Respond with plain text only."}}),e.logger.debug?.("[beeos-claw] Canvas system prompt hook registered: before_prompt_build")}catch{e.logger.debug?.("[beeos-claw] before_prompt_build hook not supported")}try{e.on("before_tool_call",(e,o)=>{if(o?.sessionKey&&l.setPendingToolCallSessionKey(o.sessionKey),!p.has(e.toolName))return;if(isThirdPartyChannel(s,{sessionKey:o?.sessionKey}))return{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."};return(o?.sessionKey?l.isCanvasActiveForGatewayKey(o.sessionKey):l.isCanvasActive())?void 0:{block:!0,blockReason:"Canvas is closed by user. Respond with plain text instead."}}),e.logger.debug?.("[beeos-claw] Canvas tool guard registered: before_tool_call")}catch{e.logger.debug?.("[beeos-claw] before_tool_call hook not supported")}}e.logger.info?.("[beeos-claw] Canvas tools registered (render_ui, close_ui, render_form + auto-action tools)")}else e.logger.info?.("[beeos-claw] Canvas disabled by config (canvasEnabled=false)");const m=new TerminalManager,b=createTerminalWebSocketService({config:{port:r.terminalWsPort,token:agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):"",allowedOrigins:r.terminalWsAllowedOrigins},terminalMgr:m,logger:e.logger,writeObs:t});if(r.bridge.shell.enabled?e.registerService({id:"beeos-terminal-ws",start(){b.start(),e.logger.info?.(`[beeos-claw] Terminal WS started on port ${r.terminalWsPort}`)},stop(){b.stop(),m.destroy(),e.logger.info?.("[beeos-claw] Terminal WS stopped")}}):e.logger.info?.("[beeos-claw] Terminal/shell disabled by config (bridge.shell.enabled=false)"),o.openclawConfig){const a=mirrorOpenClawConfig(o.openclawConfig);a.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${a.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${a.reason}`)}}const plugin={id:"beeos-claw",name:"beeos-claw",description:"BeeOS platform bridge plugin for OpenClaw. Provides file upload/download, ACP bridge, and observability.",version:PLUGIN_VERSION,register(e){if(e.registrationMode&&"full"!==e.registrationMode)return void e.logger.debug?.(`[beeos-claw] Skipping registration (mode=${e.registrationMode})`);const o=initializePlugin(e);if(!o)return;const a={current:null};registerUploadTools(e,o,a),registerServices(e,o,a),e.logger.info?.("[beeos-claw] Plugin registered successfully")}};export default plugin;
@@ -1 +1 @@
1
- export function generateActionTools(e,r,t,o,s){const n=e.list();let i=0;for(const e of n)for(const s of e.itemActions){if("rpc"!==s.type)continue;const n=`${e.domain}_${s.id}`,c=s.description??`${s.id} a ${e.domain} item`;r.registerTool({name:n,description:c,parameters:{type:"object",required:["params"],properties:{params:{type:"object",description:`Parameters for ${s.rpc}. Pass the exact object to send to the RPC.`}}},async execute(r,n){const i=n,c=i.params??i;try{const r=await t(s.rpc,c);return await o.render(e),{success:!0,result:r}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}}),i++}s.info?.(`[action-tool-gen] Registered ${i} action tools from ${n.length} descriptor(s)`)}export const RENDER_RESOURCE_TOOL_NAME="render_resource";export function buildRenderResourceDescription(e){return["Render a domain resource list on Canvas.","Shows a table with data and action buttons. Use this to display resource lists to the user.","","Available resources:",e.describeAvailable()].join("\n")}export const RENDER_RESOURCE_TOOL_SCHEMA={type:"object",required:["domain"],properties:{domain:{type:"string",description:"The domain to render (e.g. 'cron')"}}};export function createRenderResourceHandler(e,r,t){return async(o,s)=>{const n=s.domain;if(!n)return{success:!1,error:"domain is required"};const i=e.get(n);return i?(await r.render(i),t.debug?.(`[render_resource] Rendered resource: ${n}`),{success:!0,domain:n,message:`Resource list "${i.title}" rendered on Canvas.`}):{success:!1,error:`No resource descriptor for "${n}". Available:\n${e.describeAvailable()}`}}}
1
+ export function generateActionTools(e,t,o,r,s){const c=e.list();let n=0;for(const e of c)for(const s of e.itemActions){if("rpc"!==s.type)continue;const c=`${e.domain}_${s.id}`,i=s.description??`${s.id} a ${e.domain} item`;t.registerTool({name:c,description:i,parameters:{type:"object",required:["params"],properties:{params:{type:"object",description:`Parameters for ${s.rpc}. Pass the exact object to send to the RPC.`}}},async execute(t,c){const n=c,i=n.params??n;try{const t=await o(s.rpc,i);return await r.render(e),{success:!0,result:t}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}}),n++}s.info?.(`[action-tool-gen] Registered ${n} action tools from ${c.length} descriptor(s)`)}
@@ -1 +1 @@
1
- export{FormSchemaRegistry}from"./form-schema-registry.js";export{FormSurfaceBuilder}from"./form-surface-builder.js";export{RENDER_FORM_TOOL_NAME,RENDER_FORM_TOOL_SCHEMA,buildRenderFormDescription,createRenderFormHandler}from"./render-form-tool.js";export{ResourceDescriptorRegistry}from"./resource-descriptor-registry.js";export{ResourceSurfaceRenderer}from"./resource-surface-renderer.js";export{generateActionTools,RENDER_RESOURCE_TOOL_NAME,RENDER_RESOURCE_TOOL_SCHEMA,buildRenderResourceDescription,createRenderResourceHandler}from"./action-tool-generator.js";export{CRON_CREATE_SCHEMA,CRON_UPDATE_SCHEMA}from"./cron-schemas.js";export{CRON_RESOURCE}from"./cron-resource.js";
1
+ export{FormSchemaRegistry}from"./form-schema-registry.js";export{FormSurfaceBuilder}from"./form-surface-builder.js";export{RENDER_FORM_TOOL_NAME,RENDER_FORM_TOOL_SCHEMA,buildRenderFormDescription,createRenderFormHandler}from"./render-form-tool.js";export{ResourceDescriptorRegistry}from"./resource-descriptor-registry.js";export{ResourceSurfaceRenderer}from"./resource-surface-renderer.js";export{generateActionTools}from"./action-tool-generator.js";export{CRON_CREATE_SCHEMA,CRON_UPDATE_SCHEMA}from"./cron-schemas.js";export{CRON_RESOURCE}from"./cron-resource.js";
@@ -1 +1 @@
1
- import{TERMINAL_UPDATE_METHOD,parseTerminalOpenParams,parseTerminalInputParams,parseTerminalResizeParams,parseTerminalCloseParams,parseTerminalWaitParams}from"./rpc.js";import{Errors}from"../errors/index.js";export class TerminalBridgeHandler{terminalMgr;transport;config;logger;writeObs;interactiveTerminals=new Map;HEARTBEAT_TIMEOUT_MS=6e4;constructor(e){this.terminalMgr=e.terminalMgr,this.transport=e.transport,this.config=e.config,this.logger=e.logger,this.writeObs=e.writeObs}setupListeners(){this.terminalMgr.addListeners({onOutput:e=>{this.interactiveTerminals.has(e.terminalId)&&this.transport.sendNotification(TERMINAL_UPDATE_METHOD,{terminalId:e.terminalId,event:"stdout",dataBase64:e.data.toString("base64"),seq:e.seq})},onExit:e=>{const r=this.interactiveTerminals.get(e.terminalId);r&&(clearTimeout(r.heartbeatTimer),this.interactiveTerminals.delete(e.terminalId),this.transport.sendNotification(TERMINAL_UPDATE_METHOD,{terminalId:e.terminalId,event:"exit",code:e.code,signal:e.signal,...e.reason?{reason:e.reason}:{}}))},onStateChange:e=>{this.interactiveTerminals.has(e.terminalId)&&this.transport.sendNotification(TERMINAL_UPDATE_METHOD,{terminalId:e.terminalId,event:"state",from:e.from,to:e.to})}})}async handleRequest(e){switch(e.method){case"terminal/open":await this.handleTerminalOpen(e);break;case"terminal/input":this.handleTerminalInput(e);break;case"terminal/resize":this.handleTerminalResize(e);break;case"terminal/close":await this.handleTerminalClose(e);break;case"terminal/create":await this.handleTerminalCreate(e);break;case"terminal/wait":await this.handleTerminalWait(e);break;case"terminal/release":await this.handleTerminalRelease(e);break;case"terminal/kill":await this.handleTerminalKill(e);break;default:this.transport.sendAcpError(e.id,Errors.MethodNotFound,{message:`Method not found: ${e.method}`})}}handleNotification(e,r){switch(e){case"terminal/input":this.handleTerminalInputNotification(r);break;case"terminal/resize":this.handleTerminalResizeNotification(r);break;default:this.logger.debug?.(`[terminal-handler] Unknown notification: ${e}`)}}handleTerminalInputNotification(e){if(!this.config.bridge.shell.enabled)return;const r=parseTerminalInputParams(e);if(!r.ok)return;let a;if(r.value.dataBase64)try{a=Buffer.from(r.value.dataBase64,"base64")}catch{return}else a=Buffer.from(r.value.data??"","utf8");try{this.terminalMgr.writeToSession(r.value.terminalId,a),this.refreshTerminalHeartbeat(r.value.terminalId)}catch{}}handleTerminalResizeNotification(e){if(!this.config.bridge.shell.enabled)return;const r=parseTerminalResizeParams(e);if(r.ok)try{this.terminalMgr.resizeSession(r.value.terminalId,r.value.cols,r.value.rows),this.refreshTerminalHeartbeat(r.value.terminalId)}catch{}}async handleTerminalCreate(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalOpenParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);this.terminalMgr.sessionCount>=this.terminalMgr.maxConcurrentLimit&&this.logger.warn?.("[terminal-handler] Terminal quota exceeded, evicting oldest");const a=this.terminalMgr.create(r.value.command);this.writeObs({component:"terminal-handler",domain:"terminal",name:"terminal.created",severity:"info",payload:{terminalId:a.id,command:a.command,activeSessions:this.terminalMgr.sessionCount}}),this.transport.sendResult(e.id,{terminalId:a.id,command:a.command})}async handleTerminalWait(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalWaitParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);const a=this.terminalMgr.get(r.value.terminalId);a?this.transport.sendResult(e.id,{terminalId:r.value.terminalId,exitCode:a.exitCode,output:a.output.join(""),state:a.state}):this.transport.sendAcpError(e.id,Errors.TerminalNotFound)}async handleTerminalRelease(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalCloseParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);this.terminalMgr.get(r.value.terminalId)?(this.terminalMgr.release(r.value.terminalId),this.transport.sendResult(e.id,{})):this.transport.sendAcpError(e.id,Errors.TerminalNotFound)}async handleTerminalKill(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalCloseParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);const a=this.terminalMgr.get(r.value.terminalId);a?"closed"!==a.state?(this.terminalMgr.kill(r.value.terminalId,r.value.signal),this.transport.sendResult(e.id,{})):this.transport.sendAcpError(e.id,Errors.TerminalClosed):this.transport.sendAcpError(e.id,Errors.TerminalNotFound)}async handleTerminalOpen(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalOpenParams(e.params);if(r.ok)try{const a=this.terminalMgr.openSession({sessionId:r.value.sessionId,command:r.value.command,cols:r.value.cols,rows:r.value.rows,cwd:r.value.cwd}),t=setTimeout(()=>{this.interactiveTerminals.delete(a.terminalId),this.terminalMgr.closeSession(a.terminalId,"heartbeat_timeout")},this.HEARTBEAT_TIMEOUT_MS);"object"==typeof t&&"unref"in t&&t.unref(),this.interactiveTerminals.set(a.terminalId,{heartbeatTimer:t}),this.writeObs({component:"terminal-handler",domain:"terminal",name:"terminal.opened",severity:"info",payload:{terminalId:a.terminalId,shell:a.shell,cwd:a.cwd,activeSessions:this.terminalMgr.sessionCount}}),this.transport.sendResult(e.id,{terminalId:a.terminalId,command:a.shell,shell:a.shell,cwd:a.cwd,cols:a.cols,rows:a.rows,state:a.state})}catch(r){const a=r instanceof Error?r.message:String(r);a.includes("quota")?this.transport.sendAcpError(e.id,Errors.TerminalQuotaExceeded,{message:a}):this.transport.sendAcpError(e.id,Errors.GeneralError,{message:a})}else this.sendTerminalParseError(e.id,r)}handleTerminalInput(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalInputParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);let a;if(r.value.dataBase64)try{a=Buffer.from(r.value.dataBase64,"base64")}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"invalid dataBase64"})}else a=Buffer.from(r.value.data??"","utf8");try{this.terminalMgr.writeToSession(r.value.terminalId,a),this.refreshTerminalHeartbeat(r.value.terminalId),this.transport.sendResult(e.id,{})}catch(r){const a=r instanceof Error?r.message:String(r);this.transport.sendAcpError(e.id,Errors.TerminalNotFound,{message:a})}}handleTerminalResize(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalResizeParams(e.params);if(r.ok)try{this.terminalMgr.resizeSession(r.value.terminalId,r.value.cols,r.value.rows),this.refreshTerminalHeartbeat(r.value.terminalId),this.transport.sendResult(e.id,{})}catch(r){const a=r instanceof Error?r.message:String(r);this.transport.sendAcpError(e.id,Errors.TerminalNotFound,{message:a})}else this.sendTerminalParseError(e.id,r)}async handleTerminalClose(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalCloseParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);const a=this.interactiveTerminals.get(r.value.terminalId);a&&(clearTimeout(a.heartbeatTimer),this.interactiveTerminals.delete(r.value.terminalId)),this.terminalMgr.closeSession(r.value.terminalId),this.transport.sendResult(e.id,{})}refreshTerminalHeartbeat(e){const r=this.interactiveTerminals.get(e);r&&(clearTimeout(r.heartbeatTimer),r.heartbeatTimer=setTimeout(()=>{this.logger.warn?.(`[terminal-handler] Terminal heartbeat timeout terminalId=${e}`),this.interactiveTerminals.delete(e),this.terminalMgr.closeSession(e,"heartbeat_timeout")},this.HEARTBEAT_TIMEOUT_MS),"object"==typeof r.heartbeatTimer&&"unref"in r.heartbeatTimer&&r.heartbeatTimer.unref())}sendTerminalParseError(e,r){this.transport.sendAcpError(e,Errors.InvalidParams,{message:r.error.message})}destroy(){for(const e of this.interactiveTerminals.values())clearTimeout(e.heartbeatTimer);this.interactiveTerminals.clear()}}
1
+ import{TERMINAL_UPDATE_METHOD,parseTerminalOpenParams,parseTerminalInputParams,parseTerminalResizeParams,parseTerminalCloseParams,parseTerminalWaitParams}from"./rpc.js";import{Errors}from"../errors/index.js";export class TerminalBridgeHandler{terminalMgr;transport;config;logger;writeObs;interactiveTerminals=new Map;HEARTBEAT_TIMEOUT_MS=12e4;constructor(e){this.terminalMgr=e.terminalMgr,this.transport=e.transport,this.config=e.config,this.logger=e.logger,this.writeObs=e.writeObs}setupListeners(){this.terminalMgr.addListeners({onOutput:e=>{this.interactiveTerminals.has(e.terminalId)&&this.transport.sendNotification(TERMINAL_UPDATE_METHOD,{terminalId:e.terminalId,event:"stdout",dataBase64:e.data.toString("base64"),seq:e.seq})},onExit:e=>{const r=this.interactiveTerminals.get(e.terminalId);r&&(clearTimeout(r.heartbeatTimer),this.interactiveTerminals.delete(e.terminalId),this.transport.sendNotification(TERMINAL_UPDATE_METHOD,{terminalId:e.terminalId,event:"exit",code:e.code,signal:e.signal,...e.reason?{reason:e.reason}:{}}))},onStateChange:e=>{this.interactiveTerminals.has(e.terminalId)&&this.transport.sendNotification(TERMINAL_UPDATE_METHOD,{terminalId:e.terminalId,event:"state",from:e.from,to:e.to})}})}async handleRequest(e){switch(e.method){case"terminal/open":await this.handleTerminalOpen(e);break;case"terminal/input":this.handleTerminalInput(e);break;case"terminal/resize":this.handleTerminalResize(e);break;case"terminal/close":await this.handleTerminalClose(e);break;case"terminal/create":await this.handleTerminalCreate(e);break;case"terminal/wait":await this.handleTerminalWait(e);break;case"terminal/release":await this.handleTerminalRelease(e);break;case"terminal/kill":await this.handleTerminalKill(e);break;case"terminal/ping":this.handleTerminalPingRequest(e);break;default:this.transport.sendAcpError(e.id,Errors.MethodNotFound,{message:`Method not found: ${e.method}`})}}handleNotification(e,r){switch(e){case"terminal/input":this.handleTerminalInputNotification(r);break;case"terminal/resize":this.handleTerminalResizeNotification(r);break;case"terminal/ping":this.handleTerminalPingNotification(r);break;default:this.logger.debug?.(`[terminal-handler] Unknown notification: ${e}`)}}handleTerminalInputNotification(e){if(!this.config.bridge.shell.enabled)return;const r=parseTerminalInputParams(e);if(!r.ok)return;let a;if(r.value.dataBase64)try{a=Buffer.from(r.value.dataBase64,"base64")}catch{return}else a=Buffer.from(r.value.data??"","utf8");try{this.terminalMgr.writeToSession(r.value.terminalId,a),this.refreshTerminalHeartbeat(r.value.terminalId)}catch{}}handleTerminalResizeNotification(e){if(!this.config.bridge.shell.enabled)return;const r=parseTerminalResizeParams(e);if(r.ok)try{this.terminalMgr.resizeSession(r.value.terminalId,r.value.cols,r.value.rows),this.refreshTerminalHeartbeat(r.value.terminalId)}catch{}}handleTerminalPingNotification(e){const r=e;r?.terminalId&&this.refreshTerminalHeartbeat(r.terminalId)}handleTerminalPingRequest(e){const r=e.params,a=r?.terminalId;a?(this.refreshTerminalHeartbeat(a),this.transport.sendResult(e.id,{terminalId:a,status:"ok"})):this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"terminalId required"})}async handleTerminalCreate(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalOpenParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);this.terminalMgr.sessionCount>=this.terminalMgr.maxConcurrentLimit&&this.logger.warn?.("[terminal-handler] Terminal quota exceeded, evicting oldest");const a=this.terminalMgr.create(r.value.command);this.writeObs({component:"terminal-handler",domain:"terminal",name:"terminal.created",severity:"info",payload:{terminalId:a.id,command:a.command,activeSessions:this.terminalMgr.sessionCount}}),this.transport.sendResult(e.id,{terminalId:a.id,command:a.command})}async handleTerminalWait(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalWaitParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);const a=this.terminalMgr.get(r.value.terminalId);a?this.transport.sendResult(e.id,{terminalId:r.value.terminalId,exitCode:a.exitCode,output:a.output.join(""),state:a.state}):this.transport.sendAcpError(e.id,Errors.TerminalNotFound)}async handleTerminalRelease(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalCloseParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);this.terminalMgr.get(r.value.terminalId)?(this.terminalMgr.release(r.value.terminalId),this.transport.sendResult(e.id,{})):this.transport.sendAcpError(e.id,Errors.TerminalNotFound)}async handleTerminalKill(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalCloseParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);const a=this.terminalMgr.get(r.value.terminalId);a?"closed"!==a.state?(this.terminalMgr.kill(r.value.terminalId,r.value.signal),this.transport.sendResult(e.id,{})):this.transport.sendAcpError(e.id,Errors.TerminalClosed):this.transport.sendAcpError(e.id,Errors.TerminalNotFound)}async handleTerminalOpen(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalOpenParams(e.params);if(r.ok)try{const a=this.terminalMgr.openSession({sessionId:r.value.sessionId,command:r.value.command,cols:r.value.cols,rows:r.value.rows,cwd:r.value.cwd}),t=setTimeout(()=>{this.terminalMgr.closeSession(a.terminalId,"heartbeat_timeout")},this.HEARTBEAT_TIMEOUT_MS);"object"==typeof t&&"unref"in t&&t.unref(),this.interactiveTerminals.set(a.terminalId,{heartbeatTimer:t}),this.writeObs({component:"terminal-handler",domain:"terminal",name:"terminal.opened",severity:"info",payload:{terminalId:a.terminalId,shell:a.shell,cwd:a.cwd,activeSessions:this.terminalMgr.sessionCount}}),this.transport.sendResult(e.id,{terminalId:a.terminalId,command:a.shell,shell:a.shell,cwd:a.cwd,cols:a.cols,rows:a.rows,state:a.state})}catch(r){const a=r instanceof Error?r.message:String(r);a.includes("quota")?this.transport.sendAcpError(e.id,Errors.TerminalQuotaExceeded,{message:a}):this.transport.sendAcpError(e.id,Errors.GeneralError,{message:a})}else this.sendTerminalParseError(e.id,r)}handleTerminalInput(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalInputParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);let a;if(r.value.dataBase64)try{a=Buffer.from(r.value.dataBase64,"base64")}catch{return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"invalid dataBase64"})}else a=Buffer.from(r.value.data??"","utf8");try{this.terminalMgr.writeToSession(r.value.terminalId,a),this.refreshTerminalHeartbeat(r.value.terminalId),this.transport.sendResult(e.id,{})}catch(r){const a=r instanceof Error?r.message:String(r);this.transport.sendAcpError(e.id,Errors.TerminalNotFound,{message:a})}}handleTerminalResize(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalResizeParams(e.params);if(r.ok)try{this.terminalMgr.resizeSession(r.value.terminalId,r.value.cols,r.value.rows),this.refreshTerminalHeartbeat(r.value.terminalId),this.transport.sendResult(e.id,{})}catch(r){const a=r instanceof Error?r.message:String(r);this.transport.sendAcpError(e.id,Errors.TerminalNotFound,{message:a})}else this.sendTerminalParseError(e.id,r)}async handleTerminalClose(e){if(!this.config.bridge.shell.enabled)return void this.transport.sendAcpError(e.id,Errors.ShellDisabled);const r=parseTerminalCloseParams(e.params);if(!r.ok)return void this.sendTerminalParseError(e.id,r);const a=this.interactiveTerminals.get(r.value.terminalId);a&&(clearTimeout(a.heartbeatTimer),this.interactiveTerminals.delete(r.value.terminalId)),this.terminalMgr.closeSession(r.value.terminalId),this.transport.sendResult(e.id,{})}refreshTerminalHeartbeat(e){const r=this.interactiveTerminals.get(e);r&&(clearTimeout(r.heartbeatTimer),r.heartbeatTimer=setTimeout(()=>{this.logger.warn?.(`[terminal-handler] Terminal heartbeat timeout terminalId=${e}`),this.terminalMgr.closeSession(e,"heartbeat_timeout")},this.HEARTBEAT_TIMEOUT_MS),"object"==typeof r.heartbeatTimer&&"unref"in r.heartbeatTimer&&r.heartbeatTimer.unref())}sendTerminalParseError(e,r){this.transport.sendAcpError(e,Errors.InvalidParams,{message:r.error.message})}destroy(){for(const e of this.interactiveTerminals.values())clearTimeout(e.heartbeatTimer);this.interactiveTerminals.clear()}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
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>",