beeos-claw 0.5.9 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/a2a/tools.js CHANGED
@@ -1 +1 @@
1
- import{agentFetch}from"../agent-auth.js";import{resolveAgentIdFromMentions}from"./mention-resolver.js";async function a2aFetch(e,t,n){const a=`${e.agentGatewayUrl}${t}`;return e.agentKeys?agentFetch(a,e.agentKeys,n):fetch(a,n)}export{a2aFetch};export class TaskHandleRegistry{seq=0;handles=new Map;taskIdToHandle=new Map;create(e,t,n,a){const s="bth_"+ ++this.seq;return this.handles.set(s,{taskId:e,agentId:t,contextId:n,sessionKey:a}),this.taskIdToHandle.set(e,s),s}resolveTaskId(e){const t=this.handles.get(e);return t?t.taskId:e}}const defaultRegistry=new TaskHandleRegistry;export function registerA2ATools(e,t,n=defaultRegistry,a,s,r){e.registerTool({name:"beeos_discover_agents",description:"Find other agents that can help with a task. Returns a list of agents matching the query. Results include your own agents and publicly available agents.",parameters:{type:"object",properties:{query:{type:"string",description:"What capability or skill you are looking for"}},required:["query"]},async execute(e,n,a){const{query:s}=n;try{const e=await a2aFetch(t,`/api/v1/a2a/discover?query=${encodeURIComponent(s)}&limit=20`,{signal:a});if(!e.ok)return{content:JSON.stringify({error:`discover failed: ${e.status}`})};const n=await e.json();return{content:JSON.stringify(n,null,2)}}catch(e){if(a?.aborted)throw e;return{content:JSON.stringify({error:e instanceof Error?e.message:String(e)})}}}}),e.registerTool({name:"beeos_call_agent",description:"Ask another agent to perform a task. Waits for the agent to complete the task and returns the result. Timeout defaults to 10 minutes (configurable via timeoutMs).",parameters:{type:"object",properties:{agentId:{type:"string",description:"The target agent UUID (e.g. d04080d1-...) or display name. Prefer the UUID from discover results or @mention metadata when available."},message:{type:"string",description:"The task description or message to send to the agent"},taskId:{type:"string",description:"Optional existing task ID to continue a multi-turn conversation. When provided, the message is appended to the existing task instead of creating a new one."},files:{type:"array",items:{type:"string"},description:"Optional file URLs to include with the request"},contextId:{type:"string",description:"Optional context ID for multi-turn conversations with the same agent"},timeoutMs:{type:"number",description:"Task timeout in milliseconds. Defaults to 600000 (10 minutes). Set higher for long-running tasks."}},required:["agentId","message"]},async execute(e,o,i){let{agentId:c,message:d,files:g,contextId:l,timeoutMs:u,taskId:f}=o;const p=a?.(e),m=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;if(!m.test(c)){const n=s?.(e);if(n?.length){const e=resolveAgentIdFromMentions(n,c);e&&(t.logger.info?.(`beeos_call_agent: resolved "${c}" → ${e} via mention context`),c=e)}if(!m.test(c))try{const e=await a2aFetch(t,`/api/v1/a2a/discover?query=${encodeURIComponent(c)}&limit=10`,{signal:i});if(e.ok){const n=await e.json(),a=n?.data??[],s=c.toLowerCase(),r=a.find(e=>e.name.toLowerCase()===s)??a.find(e=>e.name.toLowerCase().includes(s));if(!r)return{content:JSON.stringify({error:`Could not resolve agent name "${c}". Available agents: ${a.map(e=>`${e.name} (${e.id})`).join(", ")||"none"}`})};t.logger.info?.(`beeos_call_agent: resolved "${c}" → ${r.id} (${r.name}) via discover`),c=r.id}}catch{}}const h=l||(p?`session:${p}`:""),y=u||6e5;try{const e={jsonrpc:"2.0",id:`claw-${Date.now()}`,method:"SendMessage",params:{message:{role:"user",parts:[{text:d}],...h?{contextId:h}:{},...f?{taskId:f}:{}},configuration:{timeoutMs:y}}},a=`/api/v1/a2a/${encodeURIComponent(c)}`,s=AbortSignal.timeout(y+1e4),o=i?AbortSignal.any([i,s]):s,g=await a2aFetch(t,a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:o}),u=await g.text();let m;try{m=JSON.parse(u)}catch{m={}}if(!g.ok||m.error){if(401===g.status||403===g.status){const e=m.error??{};-32600===e.code&&r&&r("_beeos/auth_required",{type:"auth_required",target_agent_id:c,target_agent_name:c,title:"Authentication Required",content:`Agent **${c}** requires authentication.`})}const e=m.error?JSON.stringify(m.error):u;return{content:JSON.stringify({error:`send task failed: ${g.status} ${e}`})}}const k=m.result,v=k?.id,I=k?.status?.state;if(v&&n.create(v,c,l,p),"completed"===I){const e=k?.artifacts,t=e?.flatMap(e=>e.parts??[]).filter(e=>"text"===e.kind).map(e=>e.text).join("\n");if(t)return{content:t}}if("failed"===I){const e=k?.status,t=e?.message?.parts,n=t?.[0]?.text??"task failed";return{content:JSON.stringify({error:n})}}return{content:JSON.stringify(k,null,2)}}catch(e){if(i?.aborted)throw e;return e instanceof DOMException&&"TimeoutError"===e.name?{content:JSON.stringify({error:`task timed out after ${y}ms`,agent:c})}:{content:JSON.stringify({error:e instanceof Error?e.message:String(e)})}}}})}
1
+ import{agentFetch}from"../agent-auth.js";import{resolveAgentIdFromMentions}from"./mention-resolver.js";async function a2aFetch(e,t,n){const a=`${e.agentGatewayUrl}${t}`;return e.agentKeys?agentFetch(a,e.agentKeys,n):fetch(a,n)}export{a2aFetch};export class TaskHandleRegistry{seq=0;handles=new Map;taskIdToHandle=new Map;create(e,t,n,a){const s="bth_"+ ++this.seq;return this.handles.set(s,{taskId:e,agentId:t,contextId:n,sessionKey:a}),this.taskIdToHandle.set(e,s),s}resolveTaskId(e){const t=this.handles.get(e);return t?t.taskId:e}}const defaultRegistry=new TaskHandleRegistry;export function registerA2ATools(e,t,n=defaultRegistry,a,s,r,i){e.registerTool({name:"beeos_discover_agents",description:"List agents available for A2A collaboration. When called without a query, returns all visible agents (your own agents + public agents). When called with a query, filters by name, description, or skill. Results include an `isSelf` flag marking the agent that represents the current instance (do not call yourself). Each result includes `instanceName` (human-readable device/instance label) when available.",parameters:{type:"object",properties:{query:{type:"string",description:"Optional keyword to filter agents by name, description, or skill. Omit to list all agents."}},required:[]},async execute(e,n,a){const{query:s}=n??{};try{const e=new URLSearchParams({limit:"50"});s&&e.set("query",s);const n=await a2aFetch(t,`/api/v1/a2a/discover?${e}`,{signal:a});if(!n.ok)return{content:JSON.stringify({error:`discover failed: ${n.status}`})};const r=await n.json(),o=i?.();if(o&&Array.isArray(r?.data))for(const e of r.data)e.isSelf=e.instance_id===o;return{content:JSON.stringify(r,null,2)}}catch(e){if(a?.aborted)throw e;return{content:JSON.stringify({error:e instanceof Error?e.message:String(e)})}}}}),e.registerTool({name:"beeos_call_agent",description:"Ask another agent to perform a task. Waits for the agent to complete the task and returns the result. Timeout defaults to 10 minutes (configurable via timeoutMs).",parameters:{type:"object",properties:{agentId:{type:"string",description:"The target agent UUID (e.g. d04080d1-...) or display name. Prefer the UUID from discover results or @mention metadata when available."},message:{type:"string",description:"The task description or message to send to the agent"},taskId:{type:"string",description:"Optional existing task ID to continue a multi-turn conversation. When provided, the message is appended to the existing task instead of creating a new one."},files:{type:"array",items:{type:"string"},description:"Optional file URLs to include with the request"},contextId:{type:"string",description:"Optional context ID for multi-turn conversations with the same agent"},timeoutMs:{type:"number",description:"Task timeout in milliseconds. Defaults to 600000 (10 minutes). Set higher for long-running tasks."}},required:["agentId","message"]},async execute(e,i,o){let{agentId:c,message:l,files:d,contextId:g,timeoutMs:u,taskId:f}=i;const m=a?.(e),p=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;if(!p.test(c)){const n=s?.(e);if(n?.length){const e=resolveAgentIdFromMentions(n,c);e&&(t.logger.info?.(`beeos_call_agent: resolved "${c}" → ${e} via mention context`),c=e)}if(!p.test(c))try{const e=await a2aFetch(t,`/api/v1/a2a/discover?query=${encodeURIComponent(c)}&limit=10`,{signal:o});if(e.ok){const n=await e.json(),a=n?.data??[],s=c.toLowerCase(),r=a.find(e=>e.name.toLowerCase()===s)??a.find(e=>e.name.toLowerCase().includes(s));if(!r)return{content:JSON.stringify({error:`Could not resolve agent name "${c}". Available agents: ${a.map(e=>`${e.name} (${e.id})`).join(", ")||"none"}`})};t.logger.info?.(`beeos_call_agent: resolved "${c}" → ${r.id} (${r.name}) via discover`),c=r.id}}catch{}}const h=g||(m?`session:${m}`:""),y=u||6e5;try{const e={jsonrpc:"2.0",id:`claw-${Date.now()}`,method:"SendMessage",params:{message:{role:"user",parts:[{text:l}],...h?{contextId:h}:{},...f?{taskId:f}:{}},configuration:{timeoutMs:y}}},a=`/api/v1/a2a/${encodeURIComponent(c)}`,s=AbortSignal.timeout(y+1e4),i=o?AbortSignal.any([o,s]):s,d=await a2aFetch(t,a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:i}),u=await d.text();let p;try{p=JSON.parse(u)}catch{p={}}if(!d.ok||p.error){if(401===d.status||403===d.status){const e=p.error??{};-32600===e.code&&r&&r("_beeos/auth_required",{type:"auth_required",target_agent_id:c,target_agent_name:c,title:"Authentication Required",content:`Agent **${c}** requires authentication.`})}const e=p.error?JSON.stringify(p.error):u;return{content:JSON.stringify({error:`send task failed: ${d.status} ${e}`})}}const k=p.result,w=k?.id,b=k?.status?.state;w&&n.create(w,c,g,m);const v=e=>{const t=e?.artifacts,n=t?.flatMap(e=>e.parts??[]).filter(e=>"text"===e.kind).map(e=>e.text).join("\n");return n||void 0};if("working"===b&&w){const e=2e3,n=Date.now()+y;for(;Date.now()<n;){if(await new Promise(t=>setTimeout(t,e)),o?.aborted)throw new DOMException("Aborted","AbortError");const n=await a2aFetch(t,`/api/v1/a2a/tasks/${encodeURIComponent(w)}`,{signal:o});if(!n.ok)break;const a=await n.json(),s=a.result??a,r=s?.status?.state;if("completed"===r){return{content:v(s)??JSON.stringify(s,null,2)}}if("failed"===r||"timeout"===r||"canceled"===r||"rejected"===r){const e=s?.status,t=e?.message?.parts,n=t?.[0]?.text??r??"task failed";return{content:JSON.stringify({error:n,task_id:w})}}}return{content:JSON.stringify({status:"working",task_id:w,message:`Task is still running. Use task_id "${w}" to check the result later.`})}}if("completed"===b){const e=v(k??{});if(e)return{content:e}}if("failed"===b){const e=k?.status,t=e?.message?.parts,n=t?.[0]?.text??"task failed";return{content:JSON.stringify({error:n})}}return{content:JSON.stringify(k,null,2)}}catch(e){if(o?.aborted)throw e;return e instanceof DOMException&&"TimeoutError"===e.name?{content:JSON.stringify({error:`task timed out after ${y}ms`,agent:c})}:{content:JSON.stringify({error:e instanceof Error?e.message:String(e)})}}}})}
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{MessageClient}from"@beeos-ai/message-sdk";import{extractChatPrompt}from"@beeos-ai/message-sdk/chat-envelope";import{createTokenProvider}from"@beeos-ai/message-sdk/auth";import{registerA2ATools,TaskHandleRegistry}from"./a2a/tools.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{handleStatusRequest}from"./status.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{setUploadDeps}from"./upload/upload-file.js";import{registerLocalFileExtractor}from"./upload/local-file-extractors.js";import{browserScreenshotExtractor}from"./browser/screenshot-extractor.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{buildCronSchemas}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,sharedCanvasState=null,_initializing=!1,_pluginRegisteredAt=0;const ED25519_PKCS8_PREFIX=Buffer.from("302e020100300506032b657004220420","hex");function agentKeyPairToIdentity(e){return{publicKeyBase64:()=>Buffer.from(e.publicKey).toString("base64"),sign:async o=>{const{createPrivateKey:a,sign:r}=await import("node:crypto"),t=a({key:32===e.privateKey.length?Buffer.concat([ED25519_PKCS8_PREFIX,Buffer.from(e.privateKey)]):Buffer.from(e.privateKey),format:"der",type:"pkcs8"});return new Uint8Array(r(null,Buffer.from(o),t))}}}let unhandledRejectionObserverInstalled=!1;function installUnhandledRejectionObserver(e,o){unhandledRejectionObserverInstalled||(unhandledRejectionObserverInstalled=!0,process.on("unhandledRejection",a=>{const r=a instanceof Error?a.message:String(a);e.logger.error?.("[beeos-claw] unhandledRejection:",a),o({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:r,error:{code:"unhandled_rejection",message:r}})}))}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}):()=>{};installUnhandledRejectionObserver(e,s),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 agentKeys&&r.agentGatewayUrl&&(setUploadDeps({gatewayUrl:r.agentGatewayUrl,keys:agentKeys,logger:e.logger}),registerLocalFileExtractor(browserScreenshotExtractor),e.logger.info?.("[beeos-claw] Upload deps + browser screenshot extractor registered")),{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,a,s){e.logger.debug?.(`[beeos_upload_file] toolCallId=${o} params=${JSON.stringify(a)}`);const n=await executeUpload(a,r,agentKeys,s);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}}),(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)}}),e.registerHttpRoute({path:"/beeos/status",auth:"plugin",handler(e,o){handleStatusRequest(e,o,{publicKey:agentKeys?.publicKey??null,pluginVersion:PLUGIN_VERSION})}})}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(){const o=Date.now(),a=_pluginRegisteredAt?o-_pluginRegisteredAt:-1;if(e.logger.info?.(`[beeos-claw] beeos-acp service start() begin, sincePluginRegister=${a}ms, bridge.url=${r.bridge.url||"(empty)"}`),!r.bridge.url&&r.agentGatewayUrl&&n){const o=Date.now(),a=await discoverBridgeFromGW(r.agentGatewayUrl,n);if(e.logger.info?.(`[beeos-claw] bridge discovery took ${Date.now()-o}ms result=${a?.url??"null"}`),a)r.bridge.url=a.url,writeDiscoveryCache({bridge:{url:a.url,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${a.url}`);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 (total service start: ${Date.now()-o}ms)`)},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=agentKeyPairToIdentity(agentKeys);y=createTokenProvider({agentGatewayUrl:r.agentGatewayUrl,identity:e})}if(d&&(y||r.messageBus.serviceUrl)){c=new MessageClient({serviceUrl:r.messageBus.serviceUrl||"https://msg.invalid",centrifugoUrl:p,tokenProvider:y,logger:e.logger});const o=u,a=async o=>{c.once("connect",()=>{l.setMessageClient(c,o),e.logger.info?.("[beeos-claw] MessageClient connected (principalId=%s, via=%s)",o,y?"gateway":"direct")}),await c.connect({identityId:o})};e.registerService({id:"beeos-message-bus",async start(){let r=o;if(r||(r=process.env.BEEOS_PRINCIPAL_ID||""),!r&&l)try{await l.waitForAgentMapping(),r=l.getDefaultPlatformAgent()?.instanceId||""}catch(o){e.logger.warn?.("[beeos-claw] Failed to resolve principalId from agent sync:",o)}r?await a(r):l?(e.logger.info?.("[beeos-claw] Message bus deferred: waiting for late agent sync..."),l.onceAgentMappingReady(()=>{const o=l.getDefaultPlatformAgent()?.instanceId;o?a(o).catch(o=>{e.logger.error?.("[beeos-claw] Deferred message bus connect failed:",o)}):e.logger.warn?.("[beeos-claw] Agent mapping ready but no principalId, message bus stays disabled")})):e.logger.warn?.("[beeos-claw] No principalId resolved, message bus disabled")},async stop(){await(c?.disconnect()),e.logger.info?.("[beeos-claw] MessageClient disconnected")}});const t=async(o,a)=>{const r=extractChatPrompt(a,{source:o,localAgentId:l.getLocalAgentId()});r&&(e.logger.debug?.(`[beeos-claw] ${o} received (sessionKey=%s, contextId=%s)`,r.sessionKey,r.contextId??"-"),await l.agentSend(r.message,r.files,r.sessionKey,void 0,r.channelId,r.messageId))};let s=!1;c.on("message",async o=>{"chat_message"===o.type?await t("chat_message",o):"agent_request"===o.type&&(s||(e.logger.warn?.("[beeos-claw] Received legacy 'agent_request' message — this path is deprecated and will be removed once all upstreams migrate to 'chat_message' (L0 AgentInvocationService)."),s=!0),await t("agent_request",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){sharedCanvasState||(sharedCanvasState=new CanvasStateManager);const o=sharedCanvasState;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()}}const t=t=>{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"})`)};e.registerService({id:"beeos-canvas-relay",async start(){try{await l.waitForAgentMapping();const o=l.getDefaultPlatformAgent();if(o?.id)return void t(o);e.logger.info?.("[beeos-claw] Canvas Relay deferred: waiting for late agent sync..."),l.onceAgentMappingReady(()=>{const o=l.getDefaultPlatformAgent();if(o?.id)try{t(o)}catch(o){e.logger.warn?.(`[beeos-claw] Deferred Canvas Relay start failed: ${o}`)}else e.logger.warn?.("[beeos-claw] Agent mapping ready but no platform agent; Canvas Relay stays disabled")})}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:e=>!e||l.isCanvasActiveForGatewayKey(e),getActiveSessionId:e=>l.getSessionIdForToolCall(e),getPlatformAgentId:e=>l.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>l.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>l.waitForAgentMapping()},n=new FormSchemaRegistry,i=buildCronSchemas(s,{capMs:r.cronMaxDurationCapMs});n.register(i.create),n.register(i.update);const g=new ResourceDescriptorRegistry;g.register(CRON_RESOURCE);const c=e=>async(o,a,r)=>(l.preMapToolCallSession(o),e(o,a));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:c(createRenderUiHandler(a))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:c(createCloseUiHandler(a))}),l.setCanvasState(o,{schemaRegistry:n,descriptorRegistry:g});const d=l.getFormBuilder();d&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(n),parameters:RENDER_FORM_TOOL_SCHEMA,execute:c(createRenderFormHandler({registry:n,builder:d,logger:e.logger}))});const p=l.getResourceRenderer();if(p){generateActionTools(g,e,async(e,o)=>l.gatewayRequest(e,o),p,e.logger)}const u='\n\nTool selection guidance:\n- browser tool: Use when the user wants to OPEN, VISIT, NAVIGATE to a webpage, take SCREENSHOTS, interact with elements (click, type), or visually inspect pages.\n- beeos_search: Use ONLY for text-based information retrieval (fact-finding, research queries). Never for page interaction or screenshots.\n- beeos_fetch: Use ONLY to extract article text from a known URL. Never for visual content or screenshots.\nWhen the user says "open", "visit", "go to", "screenshot", or mentions seeing/viewing a webpage, ALWAYS prefer the browser tool.',f=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),y=256,m=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,o)=>{if(o?.sessionKey){if(m.size>=y&&!m.has(o.sessionKey)){const e=m.keys().next().value;void 0!==e&&m.delete(e)}m.set(o.sessionKey,o.channelId)}if(isThirdPartyChannel(s,{sessionKey:o?.sessionKey,channelId:o?.channelId}))return{appendSystemContext:'This conversation is in a third-party channel (Lark/Telegram/Discord/Slack); Canvas surfaces are not visible here. Do NOT use render_ui, close_ui, or render_form. Use built-in tools directly (e.g. the `cron` tool with `action:"add"` for scheduled tasks) and respond with plain text.'};if(!o?.sessionKey)return{appendSystemContext:u};return l.isCanvasActiveForGatewayKey(o.sessionKey)?{appendSystemContext:CANVAS_SYSTEM_PROMPT+u}:{appendSystemContext:"Canvas is closed by user. Do NOT use render_ui, close_ui, or render_form. Respond with plain text only."+u}}),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),!f.has(e.toolName))return;const a=o?.sessionKey?m.get(o.sessionKey):void 0;return isThirdPartyChannel(s,{sessionKey:o?.sessionKey,channelId:a})?{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."}:o?.sessionKey?l.isCanvasActiveForGatewayKey(o.sessionKey)?void 0:{block:!0,blockReason:"Canvas is closed by user. Respond with plain text instead."}:{block:!0,blockReason:"Canvas session unknown. 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 CAPABILITY_REGISTRATION_MODES=new Set(["full","discovery","tool-discovery"]),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(!_initializing)if(!e.registrationMode||CAPABILITY_REGISTRATION_MODES.has(e.registrationMode)){_initializing=!0;try{const o=initializePlugin(e);if(!o)return;const a={current:null};registerUploadTools(e,o,a),registerServices(e,o,a),_pluginRegisteredAt=Date.now(),e.logger.info?.("[beeos-claw] Plugin registered successfully")}finally{_initializing=!1}}else e.logger.debug?.(`[beeos-claw] Skipping registration (mode=${e.registrationMode})`)}};export default plugin;
1
+ import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{MessageClient}from"@beeos-ai/message-sdk";import{extractChatPrompt}from"@beeos-ai/message-sdk/chat-envelope";import{createTokenProvider}from"@beeos-ai/message-sdk/auth";import{registerA2ATools,TaskHandleRegistry}from"./a2a/tools.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{handleStatusRequest}from"./status.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{setUploadDeps}from"./upload/upload-file.js";import{registerLocalFileExtractor}from"./upload/local-file-extractors.js";import{browserScreenshotExtractor}from"./browser/screenshot-extractor.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{buildCronSchemas}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,sharedCanvasState=null,_initializing=!1,_pluginRegisteredAt=0;const ED25519_PKCS8_PREFIX=Buffer.from("302e020100300506032b657004220420","hex");function agentKeyPairToIdentity(e){return{publicKeyBase64:()=>Buffer.from(e.publicKey).toString("base64"),sign:async o=>{const{createPrivateKey:a,sign:r}=await import("node:crypto"),t=a({key:32===e.privateKey.length?Buffer.concat([ED25519_PKCS8_PREFIX,Buffer.from(e.privateKey)]):Buffer.from(e.privateKey),format:"der",type:"pkcs8"});return new Uint8Array(r(null,Buffer.from(o),t))}}}let unhandledRejectionObserverInstalled=!1;function installUnhandledRejectionObserver(e,o){unhandledRejectionObserverInstalled||(unhandledRejectionObserverInstalled=!0,process.on("unhandledRejection",a=>{const r=a instanceof Error?a.message:String(a);e.logger.error?.("[beeos-claw] unhandledRejection:",a),o({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:r,error:{code:"unhandled_rejection",message:r}})}))}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}):()=>{};installUnhandledRejectionObserver(e,s),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 agentKeys&&r.agentGatewayUrl&&(setUploadDeps({gatewayUrl:r.agentGatewayUrl,keys:agentKeys,logger:e.logger}),registerLocalFileExtractor(browserScreenshotExtractor),e.logger.info?.("[beeos-claw] Upload deps + browser screenshot extractor registered")),{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,a,s){e.logger.debug?.(`[beeos_upload_file] toolCallId=${o} params=${JSON.stringify(a)}`);const n=await executeUpload(a,r,agentKeys,s);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}}),(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)}}),e.registerHttpRoute({path:"/beeos/status",auth:"plugin",handler(e,o){handleStatusRequest(e,o,{publicKey:agentKeys?.publicKey??null,pluginVersion:PLUGIN_VERSION})}})}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(){const o=Date.now(),a=_pluginRegisteredAt?o-_pluginRegisteredAt:-1;if(e.logger.info?.(`[beeos-claw] beeos-acp service start() begin, sincePluginRegister=${a}ms, bridge.url=${r.bridge.url||"(empty)"}`),!r.bridge.url&&r.agentGatewayUrl&&n){const o=Date.now(),a=await discoverBridgeFromGW(r.agentGatewayUrl,n);if(e.logger.info?.(`[beeos-claw] bridge discovery took ${Date.now()-o}ms result=${a?.url??"null"}`),a)r.bridge.url=a.url,writeDiscoveryCache({bridge:{url:a.url,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${a.url}`);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 (total service start: ${Date.now()-o}ms)`)},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=agentKeyPairToIdentity(agentKeys);y=createTokenProvider({agentGatewayUrl:r.agentGatewayUrl,identity:e})}if(d&&(y||r.messageBus.serviceUrl)){c=new MessageClient({serviceUrl:r.messageBus.serviceUrl||"https://msg.invalid",centrifugoUrl:p,tokenProvider:y,logger:e.logger});const o=u,a=async o=>{c.once("connect",()=>{l.setMessageClient(c,o),e.logger.info?.("[beeos-claw] MessageClient connected (principalId=%s, via=%s)",o,y?"gateway":"direct")}),await c.connect({identityId:o})};e.registerService({id:"beeos-message-bus",async start(){let r=o;if(r||(r=process.env.BEEOS_PRINCIPAL_ID||""),!r&&l)try{await l.waitForAgentMapping(),r=l.getDefaultPlatformAgent()?.instanceId||""}catch(o){e.logger.warn?.("[beeos-claw] Failed to resolve principalId from agent sync:",o)}r?await a(r):l?(e.logger.info?.("[beeos-claw] Message bus deferred: waiting for late agent sync..."),l.onceAgentMappingReady(()=>{const o=l.getDefaultPlatformAgent()?.instanceId;o?a(o).catch(o=>{e.logger.error?.("[beeos-claw] Deferred message bus connect failed:",o)}):e.logger.warn?.("[beeos-claw] Agent mapping ready but no principalId, message bus stays disabled")})):e.logger.warn?.("[beeos-claw] No principalId resolved, message bus disabled")},async stop(){await(c?.disconnect()),e.logger.info?.("[beeos-claw] MessageClient disconnected")}});const t=async(o,a)=>{const r=extractChatPrompt(a,{source:o,localAgentId:l.getLocalAgentId()});r&&(e.logger.debug?.(`[beeos-claw] ${o} received (sessionKey=%s, contextId=%s)`,r.sessionKey,r.contextId??"-"),await l.agentSend(r.message,r.files,r.sessionKey,void 0,r.channelId,r.messageId))};let s=!1;c.on("message",async o=>{"chat_message"===o.type?await t("chat_message",o):"agent_request"===o.type&&(s||(e.logger.warn?.("[beeos-claw] Received legacy 'agent_request' message — this path is deprecated and will be removed once all upstreams migrate to 'chat_message' (L0 AgentInvocationService)."),s=!0),await t("agent_request",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,()=>l?.getDefaultPlatformAgent()?.instanceId),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(r.canvasEnabled){sharedCanvasState||(sharedCanvasState=new CanvasStateManager);const o=sharedCanvasState;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()}}const t=t=>{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"})`)};e.registerService({id:"beeos-canvas-relay",async start(){try{await l.waitForAgentMapping();const o=l.getDefaultPlatformAgent();if(o?.id)return void t(o);e.logger.info?.("[beeos-claw] Canvas Relay deferred: waiting for late agent sync..."),l.onceAgentMappingReady(()=>{const o=l.getDefaultPlatformAgent();if(o?.id)try{t(o)}catch(o){e.logger.warn?.(`[beeos-claw] Deferred Canvas Relay start failed: ${o}`)}else e.logger.warn?.("[beeos-claw] Agent mapping ready but no platform agent; Canvas Relay stays disabled")})}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:e=>!e||l.isCanvasActiveForGatewayKey(e),getActiveSessionId:e=>l.getSessionIdForToolCall(e),getPlatformAgentId:e=>l.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>l.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>l.waitForAgentMapping()},n=new FormSchemaRegistry,i=buildCronSchemas(s,{capMs:r.cronMaxDurationCapMs});n.register(i.create),n.register(i.update);const g=new ResourceDescriptorRegistry;g.register(CRON_RESOURCE);const c=e=>async(o,a,r)=>(l.preMapToolCallSession(o),e(o,a));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:c(createRenderUiHandler(a))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:c(createCloseUiHandler(a))}),l.setCanvasState(o,{schemaRegistry:n,descriptorRegistry:g});const d=l.getFormBuilder();d&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(n),parameters:RENDER_FORM_TOOL_SCHEMA,execute:c(createRenderFormHandler({registry:n,builder:d,logger:e.logger}))});const p=l.getResourceRenderer();if(p){generateActionTools(g,e,async(e,o)=>l.gatewayRequest(e,o),p,e.logger)}const u='\n\nTool selection guidance:\n- browser tool: Use when the user wants to OPEN, VISIT, NAVIGATE to a webpage, take SCREENSHOTS, interact with elements (click, type), or visually inspect pages.\n- beeos_search: Use ONLY for text-based information retrieval (fact-finding, research queries). Never for page interaction or screenshots.\n- beeos_fetch: Use ONLY to extract article text from a known URL. Never for visual content or screenshots.\nWhen the user says "open", "visit", "go to", "screenshot", or mentions seeing/viewing a webpage, ALWAYS prefer the browser tool.',f=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),y=256,m=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,o)=>{if(o?.sessionKey){if(m.size>=y&&!m.has(o.sessionKey)){const e=m.keys().next().value;void 0!==e&&m.delete(e)}m.set(o.sessionKey,o.channelId)}if(isThirdPartyChannel(s,{sessionKey:o?.sessionKey,channelId:o?.channelId}))return{appendSystemContext:'This conversation is in a third-party channel (Lark/Telegram/Discord/Slack); Canvas surfaces are not visible here. Do NOT use render_ui, close_ui, or render_form. Use built-in tools directly (e.g. the `cron` tool with `action:"add"` for scheduled tasks) and respond with plain text.'};if(!o?.sessionKey)return{appendSystemContext:u};return l.isCanvasActiveForGatewayKey(o.sessionKey)?{appendSystemContext:CANVAS_SYSTEM_PROMPT+u}:{appendSystemContext:"Canvas is closed by user. Do NOT use render_ui, close_ui, or render_form. Respond with plain text only."+u}}),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),!f.has(e.toolName))return;const a=o?.sessionKey?m.get(o.sessionKey):void 0;return isThirdPartyChannel(s,{sessionKey:o?.sessionKey,channelId:a})?{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."}:o?.sessionKey?l.isCanvasActiveForGatewayKey(o.sessionKey)?void 0:{block:!0,blockReason:"Canvas is closed by user. Respond with plain text instead."}:{block:!0,blockReason:"Canvas session unknown. 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 CAPABILITY_REGISTRATION_MODES=new Set(["full","discovery","tool-discovery"]),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(!_initializing)if(!e.registrationMode||CAPABILITY_REGISTRATION_MODES.has(e.registrationMode)){_initializing=!0;try{const o=initializePlugin(e);if(!o)return;const a={current:null};registerUploadTools(e,o,a),registerServices(e,o,a),_pluginRegisteredAt=Date.now(),e.logger.info?.("[beeos-claw] Plugin registered successfully")}finally{_initializing=!1}}else e.logger.debug?.(`[beeos-claw] Skipping registration (mode=${e.registrationMode})`)}};export default plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
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>",