beeos-claw 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/acp-server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{WebSocketServer,WebSocket}from"ws";let BridgeClientCtor;import{GatewayClient}from"./gateway-client.js";import{TerminalManager}from"./terminal/session-manager.js";import{AcpTransport}from"./acp-gateway/transport.js";import{AcpGatewaySessionState}from"./acp-gateway/session-state.js";import{AcpGatewayBridgeCore}from"./acp-gateway/bridge-core.js";import{BeeosFileResolver}from"./acp-gateway/beeos-file-resolver.js";import{AcpGatewayEvents}from"./acp-gateway/gateway-events.js";import{TerminalBridgeHandler}from"./terminal/bridge-handler.js";import{syncAgentsToPlatform}from"./agent-sync.js";import{CanvasOrchestrator}from"./canvas-orchestrator.js";import{A2AOrchestrator}from"./a2a-orchestrator.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{writeDiscoveryCache}from"./discovery-cache.js";export class ACPServer{agentFetchFn;instanceId;wss=null;gateway=null;client=null;config;logger;writeObs;transport;state;core;events;terminalMgr;terminalHandler;canvasOrch;a2aOrch;pendingToolCallSessionKeys=[];thirdPartyChannels;agentSyncRetryTimer=null;bridgeInstance=null;bridgeConsecutiveFailures=0;static RE_DISCOVER_THRESHOLD=5;drainNextDeferredSend(e){this.a2aOrch.drainNextDeferredSend(e,(e,t,s,n)=>this.agentSend(e,t,s,n))}async gatewayRequest(e,t){if(!this.gateway?.isConnected)throw new Error("Gateway not connected");return this.gateway.request(e,t)}async agentSend(e,t,s,n){if(!this.gateway?.isConnected)throw new Error("Gateway not connected");const i=this.config.gateway.agentId||this.core.getDefaultAgentId();let a,r;if(s){if(a=this.state.findSessionByKey(s),!a)return void this.logger.warn?.("[acp-server] A2A result target session expired, discarding targetSessionKey=%s (known keys: %s)",s,[...this.state.sessionsByGatewayKey.keys()].join(", "));r=s,this.logger.info?.(`[acp-server] agentSend: resolved session for key=${s} sid=${a.sessionId}`)}else{const e=this.state.lastActiveSessionId;a=e?this.state.sessions.get(e):void 0,r=a?.gatewaySessionKey??`agent:${i}:a2a`,this.logger.info?.(`[acp-server] agentSend: no targetSessionKey, using fallback key=${r}`)}if(a&&!a.promptDone&&(void 0!==a.pendingPromptId||void 0!==a.currentRunId))return(a.pendingAgentSends??=[]).push({message:e,attachments:t,inputProvenance:n}),void this.logger.info?.("[acp-server] Session has active run, deferred message (queue=%d)",a.pendingAgentSends.length);const o=await this.canvasOrch.enrichMessageWithCanvasContext(e,r,this.thirdPartyChannels),c=await this.gateway.agentSend(r,o,i,t,n);return a&&c?.runId&&(this.state.resetForNewRun(a,a.sessionId,this.transport),a.currentRunId=c.runId,a.assistantStreamStartedAt=Date.now(),this.state.promptsByRunId.set(c.runId,a)),c}trackA2ARun(e,t,s){this.a2aOrch.trackA2ARun(e,t,s)}cancelA2ARunByTaskId(e){return this.a2aOrch.cancelA2ARunByTaskId(e)}emitA2ASystemNotification(e){this.a2aOrch.emitA2ASystemNotification(e)}sendBridgeMessage=e=>{const t=this.client;if(t&&t.readyState===WebSocket.OPEN)try{t.send(JSON.stringify(e))}catch(e){this.logger.debug?.("[acp-server] bridge send failed (socket may be closing):",e)}else this.logger.debug?.("[acp-server] bridge not ready, dropping message")};constructor(e,t,s,n,i,a){this.agentFetchFn=n,this.instanceId=i,this.config=e,this.logger=t,this.writeObs=s??(()=>{}),this.thirdPartyChannels=a??new Set,this.transport=new AcpTransport(this.sendBridgeMessage,this.writeObs),this.state=new AcpGatewaySessionState(this.logger);const r=this.agentFetchFn&&this.config.agentGatewayUrl?new BeeosFileResolver({agentGatewayUrl:this.config.agentGatewayUrl,agentFetch:this.agentFetchFn,writeObs:this.writeObs}):void 0;this.core=new AcpGatewayBridgeCore({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,getGateway:()=>this.gateway,instanceId:this.instanceId,thirdPartyChannels:this.thirdPartyChannels,beeosFileResolver:r}),this.canvasOrch=new CanvasOrchestrator({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,getSessionIdForToolCall:e=>this.getSessionIdForToolCall(e),gateway:null,getDefaultAgentId:()=>this.core.getDefaultAgentId(),resolvePlatformAgentId:e=>this.core.resolvePlatformAgentId(e)??""}),this.a2aOrch=new A2AOrchestrator({state:this.state,transport:this.transport,logger:this.logger}),this.events=new AcpGatewayEvents({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,completePrompt:(e,t)=>{this.core.completePrompt(e,t),this.canvasOrch.onPromptComplete(e),this.drainNextDeferredSend(e)},failPrompt:(e,t,s)=>{this.core.failPrompt(e,t,s),this.canvasOrch.onPromptFailed(),this.drainNextDeferredSend(e)},maybeCompletePromptFromChatState:(e,t)=>this.core.maybeCompletePromptFromChatState(e,t),agentFetch:this.agentFetchFn}),this.terminalMgr=new TerminalManager,this.terminalHandler=new TerminalBridgeHandler({terminalMgr:this.terminalMgr,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs}),this.terminalHandler.setupListeners(),this.core.handleTerminalRequest=e=>this.terminalHandler.handleRequest(e),this.core.handleTerminalNotification=(e,t)=>this.terminalHandler.handleNotification(e,t),this.core.onRunComplete=(e,t)=>{this.a2aOrch.onRunComplete(e,t)},this.core.onRunFailed=(e,t)=>{this.a2aOrch.onRunFailed(e,t)},this.core.handleCanvasNotification=(e,t)=>{this.canvasOrch.handleCanvasNotification(e,t)},this.core.getCanvasState=()=>this.canvasOrch.canvasState,this.core.onSessionLoaded=(e,t,s)=>{this.canvasOrch.onSessionLoaded(e,t,s)},this.core.onSessionDeleted=e=>{this.canvasOrch.onSessionDeleted(e)},this.core.onSessionCleared=(e,t)=>{this.canvasOrch.onSessionCleared(e,t)},this.core.handleSlashCommand=async e=>this.canvasOrch.handleSlashCommand(e),this.core.onPromptStart=()=>{this.canvasOrch.onPromptStart()},this.state.startSessionGc(e=>this.core.cleanupPrompt(e),e=>{this.canvasOrch.canvasState?.removeSession(e.gatewaySessionKey),this.transport.resetSessionUpdateCount(e.sessionId)})}async start(e){e&&(this.config.bridge.url=e);const t=this.config.bridge.url,s=this.config.keyFile;if(t&&!BridgeClientCtor)try{const e=await import("@beeos-ai/bridge-client");BridgeClientCtor=e.BridgeClient}catch(e){this.logger.warn?.("[acp-server] Bridge SDK not available")}await this.connectGateway(),t&&s&&BridgeClientCtor?await this.startBridgeMode(t,s):(this.logger.warn?.("[acp-server] Bridge not configured; running in standby (no ACP listener). Use handleUpgrade() for noServer mode."),this.core.markAgentSyncDone())}async startBridgeMode(e,t){if(!BridgeClientCtor)throw new Error("@beeos-ai/bridge-client not available");const s=new BridgeClientCtor({bridgeUrl:e,service:this.config.bridge.service||"acp",keyFile:t});this.bridgeInstance=s,s.on("connected",()=>{this.bridgeConsecutiveFailures=0,this.logger.info?.("[acp-server] Connected to Bridge (publicKey auth)"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"bridge.connected",severity:"info"}),this.tryAgentSync()}),s.on("disconnected",(...e)=>{const n=String(e[0]??"unknown");if(this.logger.warn?.(`[acp-server] Bridge disconnected: ${n}`),this.core.initialized=!1,this.bridgeConsecutiveFailures++,this.bridgeConsecutiveFailures>=ACPServer.RE_DISCOVER_THRESHOLD&&this.agentFetchFn&&this.config.agentGatewayUrl){this.bridgeConsecutiveFailures=0;const e=this.config.bridge.url;discoverBridgeFromGW(this.config.agentGatewayUrl,this.agentFetchFn).then(async n=>{n&&n!==e&&(this.logger.info?.(`[acp-server] Bridge re-discovery: URL changed ${e} → ${n}, switching`),this.config.bridge.url=n,writeDiscoveryCache({bridge:{url:n,discoveredAt:Date.now()}}),await s.close(!1),await this.startBridgeMode(n,t))}).catch(()=>{})}}),s.on("message",(...e)=>{const t=e[0],s="string"==typeof t?t:Buffer.from(t).toString("utf-8");try{const e=JSON.parse(s);"id"in e&&void 0!==e.id?this.core.handleRequest(e):this.core.handleNotification(e)}catch{this.logger.error?.("[acp-server] Invalid JSON-RPC message from Bridge")}}),s.on("error",(...e)=>{const t=e[0];this.logger.error?.("[acp-server] Bridge error:",t?.message)});const n={readyState:WebSocket.OPEN,send:(e,t)=>{try{s.send(e),t?.()}catch(e){t?.(e)}},close:()=>s.close(),terminate:()=>s.close(!1),on:()=>n,once:()=>n,removeListener:()=>n,removeAllListeners:()=>n,ping:()=>{}};Object.defineProperty(n,"readyState",{get:()=>"connected"===s.state?WebSocket.OPEN:WebSocket.CLOSED}),this.client=n;try{await s.connect()}catch(e){this.logger.warn?.(`[acp-server] Initial bridge connect failed (will retry in background): ${e?.message}`)}this.core.initialized=!1,this.logger.info?.("[acp-server] Running in Bridge mode (WS client)")}handleUpgrade(e,t,s){this.wss||(this.wss=new WebSocketServer({noServer:!0}),this.wss.on("connection",(e,t)=>this.handleConnection(e,t))),this.wss.handleUpgrade(e,t,s,t=>{this.wss.emit("connection",t,e)})}async tryAgentSync(e=0){if(this.agentSyncRetryTimer&&(clearTimeout(this.agentSyncRetryTimer),this.agentSyncRetryTimer=null),!this.client||this.client.readyState!==WebSocket.OPEN)return;if(!this.gateway||!this.agentFetchFn||!this.config.agentGatewayUrl)return void this.core.markAgentSyncDone();const t=()=>{if(e+1>=6)return this.logger.warn?.("[acp-server] agent sync exhausted 6 attempts — giving up"),void this.core.markAgentSyncDone();const t=Math.min(5e3*2**e,12e4);this.logger.info?.(`[acp-server] agent sync retry #${e+1} in ${Math.round(t/1e3)}s`),this.agentSyncRetryTimer=setTimeout(()=>this.tryAgentSync(e+1),t)};try{const e=await syncAgentsToPlatform({gateway:this.gateway,agentGatewayUrl:this.config.agentGatewayUrl,capabilities:{canvas:this.config.canvasEnabled,terminal:!0,fileSystem:!0,cron:!0},agentFetch:this.agentFetchFn,logger:this.logger});e?(this.core.setAgentMapping(e),this.logger.info?.(`[acp-server] agent mapping loaded: ${[...e.keys()].join(", ")}`)):t()}catch(e){this.logger.warn?.(`[acp-server] agent sync failed: ${e}`),t()}}async connectGateway(){this.gateway&&(this.gateway.stop(),this.gateway=null);const e=this.config.gateway;this.gateway=new GatewayClient({url:e.url,token:e.token||"",clientId:e.clientId,openclawCanvasCompat:!!this.config.openclawCanvasCompat,retry:{baseMs:this.config.retry.baseMs,maxMs:this.config.retry.maxMs,maxAttempts:this.config.retry.maxAttempts}}),this.gateway.on("chat",e=>this.events.handleChatEvent(e)),this.gateway.on("agent",e=>this.events.handleAgentEvent(e)),this.gateway.on("cron",e=>this.events.handleCronEvent(e));const t=this.gateway;this.canvasOrch.updateDeps({gateway:t?{get isConnected(){return t.isConnected},request:(e,t)=>this.gateway.request(e,t),agentSend:(e,t,s)=>this.gateway.agentSend(e,t,s)}:null}),this.config.openclawCanvasCompat&&(this.gateway.on("canvas",e=>this.canvasOrch.handleOpenClawCanvasEvent(e)),this.logger.info?.("[acp-server] OpenClaw native canvas compat enabled — listening for canvas events")),this.gateway.on("connected",()=>{this.logger.info?.("[acp-server] Connected to OpenClaw Gateway"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"gateway.connected",severity:"info"})}),this.gateway.on("disconnected",()=>{this.logger.warn?.("[acp-server] Gateway disconnected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"gateway.disconnected",severity:"warn"}),this.core.cleanupAllPendingPrompts()}),this.gateway.on("reconnecting",e=>{this.logger.info?.(`[acp-server] Reconnecting to Gateway attempt=${e.attempt} delay=${Math.round(e.delayMs)}ms`)}),this.gateway.on("reconnect-exhausted",e=>{this.logger.error?.(`[acp-server] Gateway reconnect exhausted after ${e} attempts`)}),this.gateway.on("error",e=>{this.logger.error?.("[acp-server] Gateway error:",e.message)}),this.gateway.on("transport",e=>{this.writeObs({component:"gateway-client",domain:"lifecycle",name:`transport.${e.event}`,severity:"connect_fail"===e.event||"error"===e.event?"warn":"debug",payload:e})}),this.gateway.on("reconnect-notify-scheduled",e=>{this.logger.info?.(`[acp-server] Server-initiated reconnect scheduled in ${e.delayMs}ms`)}),this.gateway.on("reconnect-notify-fired",()=>{this.logger.info?.("[acp-server] Server-initiated reconnect firing")});try{await this.gateway.start()}catch(e){this.logger.error?.("[acp-server] Initial Gateway connect failed:",e)}}handleConnection(e,t){if(this.client){this.logger.warn?.("[acp-server] Replacing existing ACP client with new connection (takeover)");const e=this.client;this.client=null;try{e.close(1008,"replaced by new client")}catch{e.terminate()}}this.client=e,this.core.initialized=!1,this.logger.info?.("[acp-server] ACP client connected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"client.connected",severity:"info"}),e.on("message",e=>{const t="string"==typeof e?e:e.toString("utf-8");try{const e=JSON.parse(t);"id"in e&&void 0!==e.id?this.core.handleRequest(e):this.core.handleNotification(e)}catch{this.logger.error?.("[acp-server] Invalid JSON-RPC message")}}),e.on("close",()=>{this.client===e&&(this.client=null,this.core.initialized=!1),this.logger.info?.("[acp-server] ACP client disconnected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"client.disconnected",severity:"info"})})}pushToolResultContent(e,t,s,n){this.core.pushToolResultContent(e,t,s,n)}setCanvasState(e,t){this.canvasOrch.setCanvasState(e,t)}wireRelayActionHandler(){this.canvasOrch.wireRelayActionHandler()}isCanvasActive(e){return this.canvasOrch.isCanvasActive(e)}isCanvasActiveForGatewayKey(e){return this.canvasOrch.isCanvasActiveForGatewayKey(e)}broadcastNotification(e,t){this.transport.sendNotification(e,t)}getActiveSessionId(){return this.state.lastActiveSessionId}getSessionIdForToolCall(e){const t=this.pendingToolCallSessionKeys.length>0?this.pendingToolCallSessionKeys[this.pendingToolCallSessionKeys.length-1]:void 0;if(t){const s=this.state.findSessionByKey(t);if(s?.sessionId)return e&&this.state.toolCallToSessionMap.set(e,s.sessionId),s.sessionId}if(e){const t=this.state.toolCallToSessionMap.get(e);if(t)return t;const s=this.state.toolCallIdMap.get(e);if(s&&s!==e){const e=this.state.toolCallToSessionMap.get(s);if(e)return e}const n=e.split("_");if(n.length>=3&&"tc"===n[0]){const e=n[1],t=this.state.promptsByRunId.get(e);if(t?.sessionId)return t.sessionId}}this.logger.warn?.(`[acp-server] getSessionIdForToolCall MISS: toolCallId=${e??"(none)"}, sessions=${this.state.sessions.size}, pendingKey=${t??"none"}`)}getSessionIdForActivePrompt(){return this.getSessionIdForToolCall()}setPendingToolCallSessionKey(e){e&&(this.pendingToolCallSessionKeys.push(e),this.pendingToolCallSessionKeys.length>16&&this.pendingToolCallSessionKeys.shift())}preMapToolCallSession(e){const t=this.pendingToolCallSessionKeys.length>0?this.pendingToolCallSessionKeys[this.pendingToolCallSessionKeys.length-1]:void 0;if(!t||!e)return;if(this.state.toolCallToSessionMap.has(e))return;const s=this.state.findSessionByKey(t);s?.sessionId&&this.state.toolCallToSessionMap.set(e,s.sessionId)}waitForAgentMapping(){return this.core.waitForAgentMapping()}onceAgentMappingReady(e){this.core.onceAgentMappingReady(e)}flushPendingCanvasLinks(){this.canvasOrch.flushPendingCanvasLinks()}onRelayReconnected(){this.canvasOrch.onRelayReconnected()}getDefaultPlatformAgentId(){return this.getDefaultPlatformAgent()?.id}getDefaultPlatformAgent(){const e=this.core.agentMapping;if(e&&0!==e.size)return e.values().next().value??void 0}getPlatformAgentIdForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const s=this.state.sessions.get(t);return this.core.resolvePlatformAgentId(s?.agentId??"")??s?.platformAgentId}getGatewaySessionKeyForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const s=this.state.sessions.get(t);return s?.gatewaySessionKey}getMentionsForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const s=this.state.sessions.get(t);return s?.activeMentions}getResourceRenderer(){return this.canvasOrch.resourceRenderer}getDescriptorRegistry(){return this.canvasOrch.descriptorRegistry}getFormBuilder(){return this.canvasOrch.formBuilder}getSchemaRegistry(){return this.canvasOrch.schemaRegistry}emitCanvasActionAsUserMessage(e,t){this.canvasOrch.emitCanvasActionAsUserMessage(e,t)}async stop(){this.agentSyncRetryTimer&&(clearTimeout(this.agentSyncRetryTimer),this.agentSyncRetryTimer=null),this.state.destroy(),this.a2aOrch.destroy(),this.canvasOrch.destroy(),this.gateway&&(this.gateway.removeAllListeners(),this.gateway.stop(),this.gateway=null),this.client&&(this.client.removeAllListeners(),this.client.close(),this.client=null),this.bridgeInstance=null,this.bridgeConsecutiveFailures=0,this.terminalHandler.destroy(),this.terminalMgr.destroy(),this.wss&&(this.wss.removeAllListeners(),this.wss.close(),this.wss=null),this.logger.info?.("[acp-server] Stopped"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"lifecycle.shutdown",severity:"info"})}}
|
|
1
|
+
import{WebSocketServer,WebSocket}from"ws";let BridgeClientCtor;import{GatewayClient}from"./gateway-client.js";import{TerminalManager}from"./terminal/session-manager.js";import{AcpTransport}from"./acp-gateway/transport.js";import{AcpGatewaySessionState}from"./acp-gateway/session-state.js";import{AcpGatewayBridgeCore}from"./acp-gateway/bridge-core.js";import{BeeosFileResolver}from"./acp-gateway/beeos-file-resolver.js";import{AcpGatewayEvents}from"./acp-gateway/gateway-events.js";import{TerminalBridgeHandler}from"./terminal/bridge-handler.js";import{syncAgentsToPlatform}from"./agent-sync.js";import{CanvasOrchestrator}from"./canvas-orchestrator.js";import{A2AOrchestrator}from"./a2a-orchestrator.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{writeDiscoveryCache}from"./discovery-cache.js";export class ACPServer{agentFetchFn;instanceId;wss=null;gateway=null;client=null;config;logger;writeObs;transport;state;core;events;terminalMgr;terminalHandler;canvasOrch;a2aOrch;pendingToolCallSessionKeys=[];thirdPartyChannels;agentSyncRetryTimer=null;bridgeInstance=null;bridgeConsecutiveFailures=0;static RE_DISCOVER_THRESHOLD=5;drainNextDeferredSend(e){this.a2aOrch.drainNextDeferredSend(e,(e,t,s,n)=>this.agentSend(e,t,s,n))}async gatewayRequest(e,t){if(!this.gateway?.isConnected)throw new Error("Gateway not connected");return this.gateway.request(e,t)}async agentSend(e,t,s,n){if(!this.gateway?.isConnected)throw new Error("Gateway not connected");const i=this.config.gateway.agentId||this.core.getDefaultAgentId();let a,r;if(s){if(a=this.state.findSessionByKey(s),!a)return void this.logger.warn?.("[acp-server] A2A result target session expired, discarding targetSessionKey=%s (known keys: %s)",s,[...this.state.sessionsByGatewayKey.keys()].join(", "));r=s,this.logger.info?.(`[acp-server] agentSend: resolved session for key=${s} sid=${a.sessionId}`)}else{const e=this.state.lastActiveSessionId;a=e?this.state.sessions.get(e):void 0,r=a?.gatewaySessionKey??`agent:${i}:a2a`,this.logger.info?.(`[acp-server] agentSend: no targetSessionKey, using fallback key=${r}`)}if(a&&!a.promptDone&&(void 0!==a.pendingPromptId||void 0!==a.currentRunId))return(a.pendingAgentSends??=[]).push({message:e,attachments:t,inputProvenance:n}),void this.logger.info?.("[acp-server] Session has active run, deferred message (queue=%d)",a.pendingAgentSends.length);const o=await this.canvasOrch.enrichMessageWithCanvasContext(e,r,this.thirdPartyChannels),c=await this.gateway.agentSend(r,o,i,t,n);return a&&c?.runId&&(this.state.resetForNewRun(a,a.sessionId,this.transport),a.currentRunId=c.runId,a.assistantStreamStartedAt=Date.now(),this.state.promptsByRunId.set(c.runId,a)),c}trackA2ARun(e,t,s){this.a2aOrch.trackA2ARun(e,t,s)}cancelA2ARunByTaskId(e){return this.a2aOrch.cancelA2ARunByTaskId(e)}emitA2ASystemNotification(e){this.a2aOrch.emitA2ASystemNotification(e)}sendBridgeMessage=e=>{const t=this.client;if(t&&t.readyState===WebSocket.OPEN)try{t.send(JSON.stringify(e))}catch(e){this.logger.debug?.("[acp-server] bridge send failed (socket may be closing):",e)}else this.logger.debug?.("[acp-server] bridge not ready, dropping message")};constructor(e,t,s,n,i,a){this.agentFetchFn=n,this.instanceId=i,this.config=e,this.logger=t,this.writeObs=s??(()=>{}),this.thirdPartyChannels=a??new Set,this.transport=new AcpTransport(this.sendBridgeMessage,this.writeObs),this.state=new AcpGatewaySessionState(this.logger);const r=this.agentFetchFn&&this.config.agentGatewayUrl?new BeeosFileResolver({agentGatewayUrl:this.config.agentGatewayUrl,agentFetch:this.agentFetchFn,writeObs:this.writeObs}):void 0;this.core=new AcpGatewayBridgeCore({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,getGateway:()=>this.gateway,instanceId:this.instanceId,thirdPartyChannels:this.thirdPartyChannels,beeosFileResolver:r}),this.canvasOrch=new CanvasOrchestrator({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,getSessionIdForToolCall:e=>this.getSessionIdForToolCall(e),gateway:null,getDefaultAgentId:()=>this.core.getDefaultAgentId(),resolvePlatformAgentId:e=>this.core.resolvePlatformAgentId(e)??""}),this.a2aOrch=new A2AOrchestrator({state:this.state,transport:this.transport,logger:this.logger}),this.events=new AcpGatewayEvents({state:this.state,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs,completePrompt:(e,t)=>{this.core.completePrompt(e,t),this.canvasOrch.onPromptComplete(e),this.drainNextDeferredSend(e)},failPrompt:(e,t,s)=>{this.core.failPrompt(e,t,s),this.canvasOrch.onPromptFailed(),this.drainNextDeferredSend(e)},maybeCompletePromptFromChatState:(e,t)=>this.core.maybeCompletePromptFromChatState(e,t),agentFetch:this.agentFetchFn}),this.terminalMgr=new TerminalManager,this.terminalHandler=new TerminalBridgeHandler({terminalMgr:this.terminalMgr,transport:this.transport,config:this.config,logger:this.logger,writeObs:this.writeObs}),this.terminalHandler.setupListeners(),this.core.handleTerminalRequest=e=>this.terminalHandler.handleRequest(e),this.core.handleTerminalNotification=(e,t)=>this.terminalHandler.handleNotification(e,t),this.core.onRunComplete=(e,t)=>{this.a2aOrch.onRunComplete(e,t)},this.core.onRunFailed=(e,t)=>{this.a2aOrch.onRunFailed(e,t)},this.core.handleCanvasNotification=(e,t)=>{this.canvasOrch.handleCanvasNotification(e,t)},this.core.getCanvasState=()=>this.canvasOrch.canvasState,this.core.onSessionLoaded=(e,t,s)=>{this.canvasOrch.onSessionLoaded(e,t,s)},this.core.onSessionDeleted=e=>{this.canvasOrch.onSessionDeleted(e)},this.core.onSessionCleared=(e,t)=>{this.canvasOrch.onSessionCleared(e,t)},this.core.handleSlashCommand=async e=>this.canvasOrch.handleSlashCommand(e),this.core.onPromptStart=()=>{this.canvasOrch.onPromptStart()},this.state.startSessionGc(e=>this.core.cleanupPrompt(e),e=>{this.canvasOrch.canvasState?.removeSession(e.gatewaySessionKey),this.transport.resetSessionUpdateCount(e.sessionId)})}async start(e){e&&(this.config.bridge.url=e);const t=this.config.bridge.url,s=this.config.keyFile;if(t&&!BridgeClientCtor)try{const e=await import("@beeos-ai/bridge-client");BridgeClientCtor=e.BridgeClient}catch(e){this.logger.warn?.("[acp-server] Bridge SDK not available")}await this.connectGateway(),t&&s&&BridgeClientCtor?await this.startBridgeMode(t,s):(this.logger.warn?.("[acp-server] Bridge not configured; running in standby (no ACP listener). Use handleUpgrade() for noServer mode."),this.core.markAgentSyncDone())}async startBridgeMode(e,t){if(!BridgeClientCtor)throw new Error("@beeos-ai/bridge-client not available");const s=new BridgeClientCtor({bridgeUrl:e,service:this.config.bridge.service||"acp",keyFile:t});this.bridgeInstance=s,s.on("connected",()=>{this.bridgeConsecutiveFailures=0,this.logger.info?.("[acp-server] Connected to Bridge (publicKey auth)"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"bridge.connected",severity:"info"}),this.tryAgentSync()}),s.on("disconnected",(...e)=>{const n=String(e[0]??"unknown");if(this.logger.warn?.(`[acp-server] Bridge disconnected: ${n}`),this.core.initialized=!1,this.bridgeConsecutiveFailures++,this.bridgeConsecutiveFailures>=ACPServer.RE_DISCOVER_THRESHOLD&&this.agentFetchFn&&this.config.agentGatewayUrl){this.bridgeConsecutiveFailures=0;const e=this.config.bridge.url;discoverBridgeFromGW(this.config.agentGatewayUrl,this.agentFetchFn).then(async n=>{n&&n.url!==e&&(this.logger.info?.(`[acp-server] Bridge re-discovery: URL changed ${e} → ${n.url}, switching`),this.config.bridge.url=n.url,writeDiscoveryCache({bridge:{url:n.url,discoveredAt:Date.now()}}),await s.close(!1),await this.startBridgeMode(n.url,t))}).catch(()=>{})}}),s.on("message",(...e)=>{const t=e[0],s="string"==typeof t?t:Buffer.from(t).toString("utf-8");try{const e=JSON.parse(s);"id"in e&&void 0!==e.id?this.core.handleRequest(e):this.core.handleNotification(e)}catch{this.logger.error?.("[acp-server] Invalid JSON-RPC message from Bridge")}}),s.on("error",(...e)=>{const t=e[0];this.logger.error?.("[acp-server] Bridge error:",t?.message)});const n={readyState:WebSocket.OPEN,send:(e,t)=>{try{s.send(e),t?.()}catch(e){t?.(e)}},close:()=>s.close(),terminate:()=>s.close(!1),on:()=>n,once:()=>n,removeListener:()=>n,removeAllListeners:()=>n,ping:()=>{}};Object.defineProperty(n,"readyState",{get:()=>"connected"===s.state?WebSocket.OPEN:WebSocket.CLOSED}),this.client=n;try{await s.connect()}catch(e){this.logger.warn?.(`[acp-server] Initial bridge connect failed (will retry in background): ${e?.message}`)}this.core.initialized=!1,this.logger.info?.("[acp-server] Running in Bridge mode (WS client)")}handleUpgrade(e,t,s){this.wss||(this.wss=new WebSocketServer({noServer:!0}),this.wss.on("connection",(e,t)=>this.handleConnection(e,t))),this.wss.handleUpgrade(e,t,s,t=>{this.wss.emit("connection",t,e)})}async tryAgentSync(e=0){if(this.agentSyncRetryTimer&&(clearTimeout(this.agentSyncRetryTimer),this.agentSyncRetryTimer=null),!this.client||this.client.readyState!==WebSocket.OPEN)return;if(!this.gateway||!this.agentFetchFn||!this.config.agentGatewayUrl)return void this.core.markAgentSyncDone();const t=()=>{if(e+1>=6)return this.logger.warn?.("[acp-server] agent sync exhausted 6 attempts — giving up"),void this.core.markAgentSyncDone();const t=Math.min(5e3*2**e,12e4);this.logger.info?.(`[acp-server] agent sync retry #${e+1} in ${Math.round(t/1e3)}s`),this.agentSyncRetryTimer=setTimeout(()=>this.tryAgentSync(e+1),t)};try{const e=await syncAgentsToPlatform({gateway:this.gateway,agentGatewayUrl:this.config.agentGatewayUrl,capabilities:{canvas:this.config.canvasEnabled,terminal:!0,fileSystem:!0,cron:!0},agentFetch:this.agentFetchFn,logger:this.logger});e?(this.core.setAgentMapping(e),this.logger.info?.(`[acp-server] agent mapping loaded: ${[...e.keys()].join(", ")}`)):t()}catch(e){this.logger.warn?.(`[acp-server] agent sync failed: ${e}`),t()}}async connectGateway(){this.gateway&&(this.gateway.stop(),this.gateway=null);const e=this.config.gateway;this.gateway=new GatewayClient({url:e.url,token:e.token||"",clientId:e.clientId,openclawCanvasCompat:!!this.config.openclawCanvasCompat,retry:{baseMs:this.config.retry.baseMs,maxMs:this.config.retry.maxMs,maxAttempts:this.config.retry.maxAttempts}}),this.gateway.on("chat",e=>this.events.handleChatEvent(e)),this.gateway.on("agent",e=>this.events.handleAgentEvent(e)),this.gateway.on("cron",e=>this.events.handleCronEvent(e));const t=this.gateway;this.canvasOrch.updateDeps({gateway:t?{get isConnected(){return t.isConnected},request:(e,t)=>this.gateway.request(e,t),agentSend:(e,t,s)=>this.gateway.agentSend(e,t,s)}:null}),this.config.openclawCanvasCompat&&(this.gateway.on("canvas",e=>this.canvasOrch.handleOpenClawCanvasEvent(e)),this.logger.info?.("[acp-server] OpenClaw native canvas compat enabled — listening for canvas events")),this.gateway.on("connected",()=>{this.logger.info?.("[acp-server] Connected to OpenClaw Gateway"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"gateway.connected",severity:"info"})}),this.gateway.on("disconnected",()=>{this.logger.warn?.("[acp-server] Gateway disconnected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"gateway.disconnected",severity:"warn"}),this.core.cleanupAllPendingPrompts()}),this.gateway.on("reconnecting",e=>{this.logger.info?.(`[acp-server] Reconnecting to Gateway attempt=${e.attempt} delay=${Math.round(e.delayMs)}ms`)}),this.gateway.on("reconnect-exhausted",e=>{this.logger.error?.(`[acp-server] Gateway reconnect exhausted after ${e} attempts`)}),this.gateway.on("error",e=>{this.logger.error?.("[acp-server] Gateway error:",e.message)}),this.gateway.on("transport",e=>{this.writeObs({component:"gateway-client",domain:"lifecycle",name:`transport.${e.event}`,severity:"connect_fail"===e.event||"error"===e.event?"warn":"debug",payload:e})}),this.gateway.on("reconnect-notify-scheduled",e=>{this.logger.info?.(`[acp-server] Server-initiated reconnect scheduled in ${e.delayMs}ms`)}),this.gateway.on("reconnect-notify-fired",()=>{this.logger.info?.("[acp-server] Server-initiated reconnect firing")});try{await this.gateway.start()}catch(e){this.logger.error?.("[acp-server] Initial Gateway connect failed:",e)}}handleConnection(e,t){if(this.client){this.logger.warn?.("[acp-server] Replacing existing ACP client with new connection (takeover)");const e=this.client;this.client=null;try{e.close(1008,"replaced by new client")}catch{e.terminate()}}this.client=e,this.core.initialized=!1,this.logger.info?.("[acp-server] ACP client connected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"client.connected",severity:"info"}),e.on("message",e=>{const t="string"==typeof e?e:e.toString("utf-8");try{const e=JSON.parse(t);"id"in e&&void 0!==e.id?this.core.handleRequest(e):this.core.handleNotification(e)}catch{this.logger.error?.("[acp-server] Invalid JSON-RPC message")}}),e.on("close",()=>{this.client===e&&(this.client=null,this.core.initialized=!1),this.logger.info?.("[acp-server] ACP client disconnected"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"client.disconnected",severity:"info"})})}pushToolResultContent(e,t,s,n){this.core.pushToolResultContent(e,t,s,n)}setCanvasState(e,t){this.canvasOrch.setCanvasState(e,t)}wireRelayActionHandler(){this.canvasOrch.wireRelayActionHandler()}isCanvasActive(e){return this.canvasOrch.isCanvasActive(e)}isCanvasActiveForGatewayKey(e){return this.canvasOrch.isCanvasActiveForGatewayKey(e)}broadcastNotification(e,t){this.transport.sendNotification(e,t)}getActiveSessionId(){return this.state.lastActiveSessionId}getSessionIdForToolCall(e){const t=this.pendingToolCallSessionKeys.length>0?this.pendingToolCallSessionKeys[this.pendingToolCallSessionKeys.length-1]:void 0;if(t){const s=this.state.findSessionByKey(t);if(s?.sessionId)return e&&this.state.toolCallToSessionMap.set(e,s.sessionId),s.sessionId}if(e){const t=this.state.toolCallToSessionMap.get(e);if(t)return t;const s=this.state.toolCallIdMap.get(e);if(s&&s!==e){const e=this.state.toolCallToSessionMap.get(s);if(e)return e}const n=e.split("_");if(n.length>=3&&"tc"===n[0]){const e=n[1],t=this.state.promptsByRunId.get(e);if(t?.sessionId)return t.sessionId}}this.logger.warn?.(`[acp-server] getSessionIdForToolCall MISS: toolCallId=${e??"(none)"}, sessions=${this.state.sessions.size}, pendingKey=${t??"none"}`)}getSessionIdForActivePrompt(){return this.getSessionIdForToolCall()}setPendingToolCallSessionKey(e){e&&(this.pendingToolCallSessionKeys.push(e),this.pendingToolCallSessionKeys.length>16&&this.pendingToolCallSessionKeys.shift())}preMapToolCallSession(e){const t=this.pendingToolCallSessionKeys.length>0?this.pendingToolCallSessionKeys[this.pendingToolCallSessionKeys.length-1]:void 0;if(!t||!e)return;if(this.state.toolCallToSessionMap.has(e))return;const s=this.state.findSessionByKey(t);s?.sessionId&&this.state.toolCallToSessionMap.set(e,s.sessionId)}waitForAgentMapping(){return this.core.waitForAgentMapping()}onceAgentMappingReady(e){this.core.onceAgentMappingReady(e)}flushPendingCanvasLinks(){this.canvasOrch.flushPendingCanvasLinks()}onRelayReconnected(){this.canvasOrch.onRelayReconnected()}getDefaultPlatformAgentId(){return this.getDefaultPlatformAgent()?.id}getDefaultPlatformAgent(){const e=this.core.agentMapping;if(e&&0!==e.size)return e.values().next().value??void 0}getPlatformAgentIdForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const s=this.state.sessions.get(t);return this.core.resolvePlatformAgentId(s?.agentId??"")??s?.platformAgentId}getGatewaySessionKeyForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const s=this.state.sessions.get(t);return s?.gatewaySessionKey}getMentionsForToolCall(e){const t=this.getSessionIdForToolCall(e);if(!t)return;const s=this.state.sessions.get(t);return s?.activeMentions}getResourceRenderer(){return this.canvasOrch.resourceRenderer}getDescriptorRegistry(){return this.canvasOrch.descriptorRegistry}getFormBuilder(){return this.canvasOrch.formBuilder}getSchemaRegistry(){return this.canvasOrch.schemaRegistry}emitCanvasActionAsUserMessage(e,t){this.canvasOrch.emitCanvasActionAsUserMessage(e,t)}async stop(){this.agentSyncRetryTimer&&(clearTimeout(this.agentSyncRetryTimer),this.agentSyncRetryTimer=null),this.state.destroy(),this.a2aOrch.destroy(),this.canvasOrch.destroy(),this.gateway&&(this.gateway.removeAllListeners(),this.gateway.stop(),this.gateway=null),this.client&&(this.client.removeAllListeners(),this.client.close(),this.client=null),this.bridgeInstance=null,this.bridgeConsecutiveFailures=0,this.terminalHandler.destroy(),this.terminalMgr.destroy(),this.wss&&(this.wss.removeAllListeners(),this.wss.close(),this.wss=null),this.logger.info?.("[acp-server] Stopped"),this.writeObs({component:"acp-server",domain:"lifecycle",name:"lifecycle.shutdown",severity:"info"})}}
|
package/dist/bridge-discovery.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export async function discoverBridgeFromGW(
|
|
1
|
+
export async function discoverBridgeFromGW(o,n,r){try{const i=r?`?region=${encodeURIComponent(r)}`:"",e=await n(`${o}/api/v1/bridge/config${i}`,{method:"GET"});if(e.ok){const o=await e.json();if(o.url)return{url:o.url,region:o.region}}}catch{}}
|
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}from"./surface/action-tool-generator.js";import{buildThirdPartyChannelSet,isThirdPartyChannel}from"./channel-detection.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null;function initializePlugin(e){let a,o;try{a=e.runtime?.config?.loadConfig?.()}catch{}try{o=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:a})}catch(a){return e.logger.error?.("[beeos-claw] Failed to resolve config:",a),null}const r=o.config,t=buildThirdPartyChannelSet(a);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:o.sources,openclawConfigPath:o.openclawConfigPath}});for(const a of o.validation)s({component:"plugin",domain:"config",name:"config.validation",severity:a.severity,summary:a.message,error:{code:a.code,message:a.message,nextSteps:a.nextSteps}}),"error"===a.severity?e.logger.error?.(`[beeos-claw] ${a.message}`):e.logger.warn?.(`[beeos-claw] ${a.message}`);if(o.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 a=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${a})`)}catch(a){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${a}`),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:a}}function registerUploadTools(e,a,o){const{cfg:r,writeObs:t}=a;cleanupUploadToolResultCache().catch(a=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(a)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(a,s){e.logger.debug?.(`[beeos_upload_file] toolCallId=${a} params=${JSON.stringify(s)}`);const n=await executeUpload(s,r,agentKeys);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${a} ok=${n.ok}`),t({component:"upload-tool",domain:"tool",name:n.ok?"upload.success":"upload.failure",severity:n.ok?"info":"warn",payload:{toolCallId:a,ok:n.ok,fileCount:n.files?.length??0,error:n.error}}),n.ok&&n.content&&o.current&&o.current.pushToolResultContent(a,BEEOS_UPLOAD_TOOL_NAME,n.content),(async o=>{try{await writeUploadToolResult(a,BEEOS_UPLOAD_TOOL_NAME,o),await cleanupUploadToolResultCache()}catch(o){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${a}: ${String(o)}`)}return o})(n)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,o){await handleFileStage(e,o,a.cfg)}})}function registerServices(e,a,o){const{cfg:r,writeObs:t,thirdPartyChannels:s}=a,n=agentKeys?(e,a)=>agentFetch(e,agentKeys,a):void 0,i=agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):void 0,l=new ACPServer(r,e.logger,t,n,i,s);o.current=l,e.registerService({id:"beeos-acp",async start(){if(!r.bridge.url&&r.agentGatewayUrl&&n){const a=await discoverBridgeFromGW(r.agentGatewayUrl,n);if(a)r.bridge.url=a,writeDiscoveryCache({bridge:{url:a,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${a}`);else{const a=readDiscoveryCache();a?.bridge?.url?(r.bridge.url=a.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${a.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:y}=r.messageBus;let u;if(r.agentGatewayUrl&&agentKeys){const e=r.agentGatewayUrl,a=agentKeys;u=async o=>{const r=await agentFetch(`${e}/api/v1/messaging/token`,a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({principal_id:o})});if(!r.ok){const e=await r.text().catch(()=>"");throw new Error(`Gateway messaging token returned ${r.status}: ${e}`)}return r.json()}}if(d&&(u||r.messageBus.serviceUrl)){c=new MessageSDK({serviceUrl:u?void 0:r.messageBus.serviceUrl,centrifugoUrl:p},e.logger);const a=f;e.registerService({id:"beeos-message-bus",async start(){let o=a;if(o||(o=process.env.BEEOS_PRINCIPAL_ID||""),!o&&l)try{await l.waitForAgentMapping(),o=l.getDefaultPlatformAgentId()||""}catch(a){e.logger.warn?.("[beeos-claw] Failed to resolve principalId from agent sync:",a)}o?(await c.connect(o,{centrifugoUrl:p,tokenProvider:u}),e.logger.info?.("[beeos-claw] Message SDK connected (principalId=%s, via=%s)",o,u?"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 o=(e,a,o,r)=>l.agentSend(e,a,o,r),t=(e,a,o)=>l.trackA2ARun(e,a,o),s=e=>l.emitA2ASystemNotification(e),n=r.agentGatewayUrl?{agentGatewayUrl:r.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger}:void 0;c.onMessage("a2a_task",createA2ATaskHandler(o,n,t,s)),c.onMessage("a2a_result",createA2AResultHandler(o,g,s)),c.onMessage("a2a_task_cancel",createA2ATaskCancelHandler(o,e=>l.cancelA2ARunByTaskId(e))),n&&e.registerService({id:"beeos-a2a-poll",async start(){await l.waitForAgentMapping();const a=await pollPendingTasks(n,o,t);a>0&&e.logger.info?.("[beeos-claw] Dispatched %d pending A2A tasks on startup",a)}})}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!==y&&r.agentGatewayUrl){const a=e=>l.getGatewaySessionKeyForToolCall(e),o=e=>l.getMentionsForToolCall(e),t=(e,a)=>{l.broadcastNotification(e,a)};registerA2ATools(e,{agentGatewayUrl:r.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},g,a,o,t),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(r.canvasEnabled){const a=new CanvasStateManager;if(r.agentGatewayUrl&&agentKeys){let o;a.setLogger(e.logger);{const e=r.agentGatewayUrl,a=agentKeys;o=async()=>{const o=await agentFetch(`${e}/api/v1/canvas/token`,a,{method:"POST"});if(!o.ok){const e=await o.text().catch(()=>"");throw new Error(`Canvas token returned ${o.status}: ${e}`)}return o.json()}}e.registerService({id:"beeos-canvas-relay",async start(){const t=()=>{try{const t=l.getDefaultPlatformAgent();if(!t?.id)return void e.logger.info?.("[beeos-claw] Canvas Relay skipped: no agentId available");const s=new CanvasRelayClient({canvasRelayUrl:r.canvasRelayUrl,agentKeys:agentKeys,agentId:t.id,displayName:t.displayName,avatarUrl:t.avatarUrl,tokenProvider:o,onReconnect:()=>l.onRelayReconnected(),logger:e.logger});s.connect(),a.setRelayClient(s),a.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(a){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${a}`)}};try{await l.waitForAgentMapping();const a=l.getDefaultPlatformAgent();a?.id?t():(e.logger.info?.("[beeos-claw] Canvas Relay deferred: waiting for late agent sync..."),l.onceAgentMappingReady(()=>{e.logger.info?.("[beeos-claw] Canvas Relay: agent mapping now available, starting relay client"),t()}))}catch(a){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${a}`)}},stop(){a.getRelayClient()?.disconnect()}}),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const o={stateManager:a,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(a,o)=>(l.preMapToolCallSession(a),e(a,o));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:g(createRenderUiHandler(o))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:g(createCloseUiHandler(o))}),l.setCanvasState(a,{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,a)=>l.gatewayRequest(e,a),d,e.logger)}const p=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),f=256,y=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,a)=>{if(a?.sessionKey){if(y.size>=f&&!y.has(a.sessionKey)){const e=y.keys().next().value;void 0!==e&&y.delete(e)}y.set(a.sessionKey,a.channelId)}if(isThirdPartyChannel(s,{sessionKey:a?.sessionKey,channelId:a?.channelId}))return;return(a?.sessionKey?l.isCanvasActiveForGatewayKey(a.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,a)=>{if(a?.sessionKey&&l.setPendingToolCallSessionKey(a.sessionKey),!p.has(e.toolName))return;if(isThirdPartyChannel(s,{sessionKey:a?.sessionKey}))return{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."};return(a?.sessionKey?l.isCanvasActiveForGatewayKey(a.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)"),a.openclawConfig){const o=mirrorOpenClawConfig(a.openclawConfig);o.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${o.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${o.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 a=initializePlugin(e);if(!a)return;const o={current:null};registerUploadTools(e,a,o),registerServices(e,a,o),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 a,o;try{a=e.runtime?.config?.loadConfig?.()}catch{}try{o=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:a})}catch(a){return e.logger.error?.("[beeos-claw] Failed to resolve config:",a),null}const r=o.config,t=buildThirdPartyChannelSet(a);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:o.sources,openclawConfigPath:o.openclawConfigPath}});for(const a of o.validation)s({component:"plugin",domain:"config",name:"config.validation",severity:a.severity,summary:a.message,error:{code:a.code,message:a.message,nextSteps:a.nextSteps}}),"error"===a.severity?e.logger.error?.(`[beeos-claw] ${a.message}`):e.logger.warn?.(`[beeos-claw] ${a.message}`);if(o.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 a=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${a})`)}catch(a){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${a}`),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:a}}function registerUploadTools(e,a,o){const{cfg:r,writeObs:t}=a;cleanupUploadToolResultCache().catch(a=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(a)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(a,s){e.logger.debug?.(`[beeos_upload_file] toolCallId=${a} params=${JSON.stringify(s)}`);const n=await executeUpload(s,r,agentKeys);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${a} ok=${n.ok}`),t({component:"upload-tool",domain:"tool",name:n.ok?"upload.success":"upload.failure",severity:n.ok?"info":"warn",payload:{toolCallId:a,ok:n.ok,fileCount:n.files?.length??0,error:n.error}}),n.ok&&n.content&&o.current&&o.current.pushToolResultContent(a,BEEOS_UPLOAD_TOOL_NAME,n.content),(async o=>{try{await writeUploadToolResult(a,BEEOS_UPLOAD_TOOL_NAME,o),await cleanupUploadToolResultCache()}catch(o){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${a}: ${String(o)}`)}return o})(n)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,o){await handleFileStage(e,o,a.cfg)}})}function registerServices(e,a,o){const{cfg:r,writeObs:t,thirdPartyChannels:s}=a,n=agentKeys?(e,a)=>agentFetch(e,agentKeys,a):void 0,i=agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):void 0,l=new ACPServer(r,e.logger,t,n,i,s);o.current=l,e.registerService({id:"beeos-acp",async start(){if(!r.bridge.url&&r.agentGatewayUrl&&n){const a=await discoverBridgeFromGW(r.agentGatewayUrl,n);if(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} (region: ${a.region??"any"})`);else{const a=readDiscoveryCache();a?.bridge?.url?(r.bridge.url=a.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${a.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:y}=r.messageBus;let u;if(r.agentGatewayUrl&&agentKeys){const e=r.agentGatewayUrl,a=agentKeys;u=async o=>{const r=await agentFetch(`${e}/api/v1/messaging/token`,a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({principal_id:o})});if(!r.ok){const e=await r.text().catch(()=>"");throw new Error(`Gateway messaging token returned ${r.status}: ${e}`)}return r.json()}}if(d&&(u||r.messageBus.serviceUrl)){c=new MessageSDK({serviceUrl:u?void 0:r.messageBus.serviceUrl,centrifugoUrl:p},e.logger);const a=f;e.registerService({id:"beeos-message-bus",async start(){let o=a;if(o||(o=process.env.BEEOS_PRINCIPAL_ID||""),!o&&l)try{await l.waitForAgentMapping(),o=l.getDefaultPlatformAgentId()||""}catch(a){e.logger.warn?.("[beeos-claw] Failed to resolve principalId from agent sync:",a)}o?(await c.connect(o,{centrifugoUrl:p,tokenProvider:u}),e.logger.info?.("[beeos-claw] Message SDK connected (principalId=%s, via=%s)",o,u?"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 o=(e,a,o,r)=>l.agentSend(e,a,o,r),t=(e,a,o)=>l.trackA2ARun(e,a,o),s=e=>l.emitA2ASystemNotification(e),n=r.agentGatewayUrl?{agentGatewayUrl:r.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger}:void 0;c.onMessage("a2a_task",createA2ATaskHandler(o,n,t,s)),c.onMessage("a2a_result",createA2AResultHandler(o,g,s)),c.onMessage("a2a_task_cancel",createA2ATaskCancelHandler(o,e=>l.cancelA2ARunByTaskId(e))),n&&e.registerService({id:"beeos-a2a-poll",async start(){await l.waitForAgentMapping();const a=await pollPendingTasks(n,o,t);a>0&&e.logger.info?.("[beeos-claw] Dispatched %d pending A2A tasks on startup",a)}})}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!==y&&r.agentGatewayUrl){const a=e=>l.getGatewaySessionKeyForToolCall(e),o=e=>l.getMentionsForToolCall(e),t=(e,a)=>{l.broadcastNotification(e,a)};registerA2ATools(e,{agentGatewayUrl:r.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},g,a,o,t),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(r.canvasEnabled){const a=new CanvasStateManager;if(r.agentGatewayUrl&&agentKeys){let o;a.setLogger(e.logger);{const e=r.agentGatewayUrl,a=agentKeys;o=async()=>{const o=await agentFetch(`${e}/api/v1/canvas/token`,a,{method:"POST"});if(!o.ok){const e=await o.text().catch(()=>"");throw new Error(`Canvas token returned ${o.status}: ${e}`)}return o.json()}}e.registerService({id:"beeos-canvas-relay",async start(){const t=()=>{try{const t=l.getDefaultPlatformAgent();if(!t?.id)return void e.logger.info?.("[beeos-claw] Canvas Relay skipped: no agentId available");const s=new CanvasRelayClient({canvasRelayUrl:r.canvasRelayUrl,agentKeys:agentKeys,agentId:t.id,displayName:t.displayName,avatarUrl:t.avatarUrl,tokenProvider:o,onReconnect:()=>l.onRelayReconnected(),logger:e.logger});s.connect(),a.setRelayClient(s),a.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(a){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${a}`)}};try{await l.waitForAgentMapping();const a=l.getDefaultPlatformAgent();a?.id?t():(e.logger.info?.("[beeos-claw] Canvas Relay deferred: waiting for late agent sync..."),l.onceAgentMappingReady(()=>{e.logger.info?.("[beeos-claw] Canvas Relay: agent mapping now available, starting relay client"),t()}))}catch(a){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${a}`)}},stop(){a.getRelayClient()?.disconnect()}}),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const o={stateManager:a,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(a,o)=>(l.preMapToolCallSession(a),e(a,o));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:g(createRenderUiHandler(o))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:g(createCloseUiHandler(o))}),l.setCanvasState(a,{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,a)=>l.gatewayRequest(e,a),d,e.logger)}const p=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),f=256,y=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,a)=>{if(a?.sessionKey){if(y.size>=f&&!y.has(a.sessionKey)){const e=y.keys().next().value;void 0!==e&&y.delete(e)}y.set(a.sessionKey,a.channelId)}if(isThirdPartyChannel(s,{sessionKey:a?.sessionKey,channelId:a?.channelId}))return;return(a?.sessionKey?l.isCanvasActiveForGatewayKey(a.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,a)=>{if(a?.sessionKey&&l.setPendingToolCallSessionKey(a.sessionKey),!p.has(e.toolName))return;if(isThirdPartyChannel(s,{sessionKey:a?.sessionKey}))return{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."};return(a?.sessionKey?l.isCanvasActiveForGatewayKey(a.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)"),a.openclawConfig){const o=mirrorOpenClawConfig(a.openclawConfig);o.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${o.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${o.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 a=initializePlugin(e);if(!a)return;const o={current:null};registerUploadTools(e,a,o),registerServices(e,a,o),e.logger.info?.("[beeos-claw] Plugin registered successfully")}};export default plugin;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{spawn}from"node:child_process";import{createRequire}from"node:module";import{randomUUID}from"node:crypto";import{realpathSync}from"node:fs";import{dirname,resolve}from"node:path";export const TERMINAL_TIMEOUT_REASONS={idle:"idle_timeout",maxDuration:"max_duration_timeout"};const ALLOWED_STATE_TRANSITIONS={opening:["running","closing","closed"],running:["closing","closed"],closing:["closed"],closed:[]},require=createRequire(import.meta.url);let cachedNodePty;function resolveOpenclawNodeModulesDir(){const e=process.argv[1];if(e)try{const t=realpathSync(e);if(/openclaw/i.test(t))return dirname(t)}catch{}const t=["/opt/homebrew/lib/node_modules/openclaw","/usr/local/lib/node_modules/openclaw","/usr/lib/node_modules/openclaw"];for(const e of t)try{return realpathSync(resolve(e,"package.json")),e}catch{}return null}function getNodePty(){if(void 0!==cachedNodePty)return cachedNodePty;const e=["node-pty","@lydell/node-pty"];for(const t of e)try{return cachedNodePty=require(t),cachedNodePty}catch{}const t=resolveOpenclawNodeModulesDir();if(t){const s=createRequire(resolve(t,"package.json"));for(const t of e)try{return cachedNodePty=s(t),cachedNodePty}catch{}}return cachedNodePty=null,null}function createPtyProcess(e){const t=getNodePty();if(!t)throw new Error("node-pty not available");const s={...process.env,TERM:"xterm-256color"},i=t.spawn(e.shell,[],{name:"xterm-256color",cwd:e.cwd,env:s,cols:e.cols,rows:e.rows});let o=!1;return i.onData(t=>{e.onData(Buffer.from(t,"utf8"))}),i.onExit(t=>{var s;s={code:Number.isFinite(t.exitCode)?t.exitCode:null,signal:null!=t.signal?String(t.signal):null},o||(o=!0,e.onExit(s))}),{write(e){const t=e.toString("utf8");t&&i.write(t)},resize(e,t){i.resize(e,t)},close(){try{i.kill()}catch{}}}}function createPipeProcess(e){const t=spawn(e.shell,[],{shell:!0,cwd:e.cwd,stdio:["pipe","pipe","pipe"],env:{...process.env,TERM:"xterm-256color"}});let s=!1;const i=t=>{s||(s=!0,e.onExit(t))};return t.stdout?.on("data",t=>e.onData(Buffer.isBuffer(t)?t:Buffer.from(t))),t.stderr?.on("data",t=>e.onData(Buffer.isBuffer(t)?t:Buffer.from(t))),t.once("error",e=>i({code:null,signal:null})),t.once("exit",(e,t)=>i({code:e??null,signal:t??null})),{write(e){t.stdin?.destroyed||t.stdin?.write(e)},resize(e,t){},close(){t.killed||t.kill()}}}function createDefaultProcess(e){try{return createPtyProcess(e)}catch{return createPipeProcess(e)}}const DEFAULT_MAX_CONCURRENT=10,DEFAULT_IDLE_TIMEOUT_MS=432e5,DEFAULT_MAX_DURATION_MS=432e5;export class TerminalNotFoundError extends Error{terminalId;constructor(e){super(`terminal not found: ${e}`),this.name="TerminalNotFoundError",this.terminalId=e}}export class TerminalClosedError extends Error{terminalId;state;constructor(e,t){super(`terminal is not writable in state=${t}: ${e}`),this.name="TerminalClosedError",this.terminalId=e,this.state=t}}function chainHandler(e,t){return t?e?s=>{e(s),t(s)}:t:e}export class TerminalManager{sessions=new Map;maxConcurrent;idleTimeoutMs;maxDurationMs;onOutput;onExit;onStateChange;constructor(e){this.maxConcurrent=e?.maxConcurrent??10,this.idleTimeoutMs=e?.idleTimeoutMs??432e5,this.maxDurationMs=e?.maxDurationMs??432e5}addListeners(e){this.onOutput=chainHandler(this.onOutput,e.onOutput),this.onExit=chainHandler(this.onExit,e.onExit),this.onStateChange=chainHandler(this.onStateChange,e.onStateChange)}openSession(e){if(this.sessions.size>=this.maxConcurrent&&this.evictOldest(),this.sessions.size>=this.maxConcurrent)throw new Error("terminal quota exceeded");const t=e.terminalId&&!this.sessions.has(e.terminalId)?e.terminalId:`term_${randomUUID().slice(0,12)}`,s=e.sessionId??t,i=e.command||process.env.SHELL||"/bin/bash",o=e.cwd||process.cwd(),n=Date.now(),r={terminalId:t,sessionId:s,shell:i,cwd:o,startedAt:n,lastActiveAt:n,cols:e.cols,rows:e.rows,state:"opening",nextSeq:1,output:[],process:null};this.sessions.set(t,r);try{r.process=createDefaultProcess({shell:i,cwd:o,cols:e.cols,rows:e.rows,onData:e=>this.handleProcessData(t,e),onExit:e=>this.handleProcessExit(t,e)}),this.transitionState(r,"running"),this.scheduleIdleTimeout(r),this.scheduleMaxDuration(r)}catch(e){throw this.clearTimers(r),this.transitionState(r,"closed"),this.sessions.delete(t),e}return this.snapshot(r)}writeToSession(e,t){const s=this.requireSession(e);this.assertRunning(s),s.process?.write(t),this.markActivity(s)}resizeSession(e,t,s){const i=this.requireSession(e);this.assertRunning(i),i.cols=t,i.rows=s,i.process?.resize(t,s),this.markActivity(i)}closeSession(e,t){const s=this.sessions.get(e);s&&(t&&!s.timeoutReason&&(s.timeoutReason=t),"closed"!==s.state?(this.clearTimers(s),"closing"!==s.state&&(this.transitionState(s,"closing"),s.process?.close())):this.clearTimers(s))}touchSession(e){const t=this.requireSession(e);this.assertRunning(t),this.markActivity(t)}getSession(e){const t=this.sessions.get(e);return t?this.snapshot(t):void 0}create(e){const t=this.openSession({command:e,cols:80,rows:24}),s=this.sessions.get(t.terminalId);return{id:s.terminalId,command:s.shell,state:s.state,output:s.output,exitCode:s.exitCode,seq:s.nextSeq-1}}get(e){const t=this.sessions.get(e);if(t)return{id:t.terminalId,state:t.state,output:t.output,exitCode:t.exitCode,seq:t.nextSeq-1}}write(e,t){try{return this.writeToSession(e,Buffer.from(t,"utf8")),!0}catch{return!1}}resize(e,t,s){try{return this.resizeSession(e,t,s),!0}catch{return!1}}kill(e,t="SIGTERM"){const s=this.sessions.get(e);s&&s.process&&("closing"!==s.state&&this.transitionState(s,"closing"),s.process.close())}release(e){const t=this.sessions.get(e);t&&(!t.process||"running"!==t.state&&"opening"!==t.state||this.kill(e),this.clearTimers(t),this.sessions.delete(e))}listSessions(){return[...this.sessions.values()].map(e=>this.snapshot(e))}get sessionCount(){return this.sessions.size}get maxConcurrentLimit(){return this.maxConcurrent}destroy(){for(const e of[...this.sessions.keys()])this.release(e)}snapshot(e){return{terminalId:e.terminalId,sessionId:e.sessionId,shell:e.shell,cwd:e.cwd,startedAt:e.startedAt,lastActiveAt:e.lastActiveAt,cols:e.cols,rows:e.rows,state:e.state}}requireSession(e){const t=this.sessions.get(e);if(!t)throw new TerminalNotFoundError(e);return t}assertRunning(e){if("running"!==e.state)throw new TerminalClosedError(e.terminalId,e.state)}transitionState(e,t){const s=e.state;s!==t&&ALLOWED_STATE_TRANSITIONS[s].includes(t)&&(e.state=t,this.onStateChange?.({terminalId:e.terminalId,sessionId:e.sessionId,from:s,to:t}))}handleProcessData(e,t){const s=this.sessions.get(e);if(!s||"closed"===s.state)return;this.markActivity(s);const i=s.nextSeq++;s.output.push(t.toString("utf-8")),this.onOutput?.({terminalId:s.terminalId,sessionId:s.sessionId,data:t,seq:i})}handleProcessExit(e,t){const s=this.sessions.get(e);s&&(this.clearTimers(s),s.exitCode=t.code??void 0,"closing"!==s.state&&this.transitionState(s,"closing"),this.transitionState(s,"closed"),this.onExit?.({terminalId:s.terminalId,sessionId:s.sessionId,code:t.code,signal:t.signal,...s.timeoutReason?{reason:s.timeoutReason}:{}}))}markActivity(e){e.lastActiveAt=Date.now(),"running"===e.state&&this.refreshIdleTimer(e)}evictOldest(){let e=null;for(const t of this.sessions.values()){if("closed"===t.state){e=t;break}(!e||t.lastActiveAt<e.lastActiveAt||t.lastActiveAt===e.lastActiveAt&&t.startedAt<e.startedAt)&&(e=t)}e&&this.release(e.terminalId)}refreshIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{"running"===e.state&&this.closeSession(e.terminalId,TERMINAL_TIMEOUT_REASONS.idle)},this.idleTimeoutMs),"object"==typeof e.idleTimer&&"unref"in e.idleTimer&&e.idleTimer.unref()}scheduleIdleTimeout(e){this.refreshIdleTimer(e)}scheduleMaxDuration(e){e.maxDurationTimer=setTimeout(()=>{"running"===e.state&&this.closeSession(e.terminalId,TERMINAL_TIMEOUT_REASONS.maxDuration)},this.maxDurationMs),"object"==typeof e.maxDurationTimer&&"unref"in e.maxDurationTimer&&e.maxDurationTimer.unref()}clearTimers(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=void 0),e.maxDurationTimer&&(clearTimeout(e.maxDurationTimer),e.maxDurationTimer=void 0)}}
|
|
1
|
+
import{spawn}from"node:child_process";import{createRequire}from"node:module";import{randomUUID}from"node:crypto";import{realpathSync}from"node:fs";import{dirname,resolve}from"node:path";export const TERMINAL_TIMEOUT_REASONS={idle:"idle_timeout",maxDuration:"max_duration_timeout"};const ALLOWED_STATE_TRANSITIONS={opening:["running","closing","closed"],running:["closing","closed"],closing:["closed"],closed:[]},require=createRequire(import.meta.url);let cachedNodePty;function resolveOpenclawNodeModulesDir(){const e=process.argv[1];if(e)try{const t=realpathSync(e);if(/openclaw/i.test(t))return dirname(t)}catch{}const t=["/opt/homebrew/lib/node_modules/openclaw","/usr/local/lib/node_modules/openclaw","/usr/lib/node_modules/openclaw"];for(const e of t)try{return realpathSync(resolve(e,"package.json")),e}catch{}return null}function getNodePty(){if(void 0!==cachedNodePty)return cachedNodePty;const e=["node-pty","@lydell/node-pty"];for(const t of e)try{return cachedNodePty=require(t),cachedNodePty}catch{}const t=resolveOpenclawNodeModulesDir();if(t){const s=createRequire(resolve(t,"package.json"));for(const t of e)try{return cachedNodePty=s(t),cachedNodePty}catch{}}return cachedNodePty=null,null}function createPtyProcess(e){const t=getNodePty();if(!t)throw new Error("node-pty not available");const s={...process.env,TERM:"xterm-256color",LANG:process.env.LANG||"C.UTF-8",LC_ALL:process.env.LC_ALL||"C.UTF-8"},i=t.spawn(e.shell,[],{name:"xterm-256color",cwd:e.cwd,env:s,cols:e.cols,rows:e.rows});let o=!1;return i.onData(t=>{e.onData(Buffer.from(t,"utf8"))}),i.onExit(t=>{var s;s={code:Number.isFinite(t.exitCode)?t.exitCode:null,signal:null!=t.signal?String(t.signal):null},o||(o=!0,e.onExit(s))}),{write(e){const t=e.toString("utf8");t&&i.write(t)},resize(e,t){i.resize(e,t)},close(){try{i.kill()}catch{}}}}function createPipeProcess(e){const t=spawn(e.shell,[],{shell:!0,cwd:e.cwd,stdio:["pipe","pipe","pipe"],env:{...process.env,TERM:"xterm-256color",LANG:process.env.LANG||"C.UTF-8",LC_ALL:process.env.LC_ALL||"C.UTF-8"}});let s=!1;const i=t=>{s||(s=!0,e.onExit(t))};return t.stdout?.on("data",t=>e.onData(Buffer.isBuffer(t)?t:Buffer.from(t))),t.stderr?.on("data",t=>e.onData(Buffer.isBuffer(t)?t:Buffer.from(t))),t.once("error",e=>i({code:null,signal:null})),t.once("exit",(e,t)=>i({code:e??null,signal:t??null})),{write(e){t.stdin?.destroyed||t.stdin?.write(e)},resize(e,t){},close(){t.killed||t.kill()}}}function createDefaultProcess(e){try{return createPtyProcess(e)}catch{return createPipeProcess(e)}}const DEFAULT_MAX_CONCURRENT=10,DEFAULT_IDLE_TIMEOUT_MS=432e5,DEFAULT_MAX_DURATION_MS=432e5;export class TerminalNotFoundError extends Error{terminalId;constructor(e){super(`terminal not found: ${e}`),this.name="TerminalNotFoundError",this.terminalId=e}}export class TerminalClosedError extends Error{terminalId;state;constructor(e,t){super(`terminal is not writable in state=${t}: ${e}`),this.name="TerminalClosedError",this.terminalId=e,this.state=t}}function chainHandler(e,t){return t?e?s=>{e(s),t(s)}:t:e}export class TerminalManager{sessions=new Map;maxConcurrent;idleTimeoutMs;maxDurationMs;onOutput;onExit;onStateChange;constructor(e){this.maxConcurrent=e?.maxConcurrent??10,this.idleTimeoutMs=e?.idleTimeoutMs??432e5,this.maxDurationMs=e?.maxDurationMs??432e5}addListeners(e){this.onOutput=chainHandler(this.onOutput,e.onOutput),this.onExit=chainHandler(this.onExit,e.onExit),this.onStateChange=chainHandler(this.onStateChange,e.onStateChange)}openSession(e){if(this.sessions.size>=this.maxConcurrent&&this.evictOldest(),this.sessions.size>=this.maxConcurrent)throw new Error("terminal quota exceeded");const t=e.terminalId&&!this.sessions.has(e.terminalId)?e.terminalId:`term_${randomUUID().slice(0,12)}`,s=e.sessionId??t,i=e.command||process.env.SHELL||"/bin/bash",o=e.cwd||process.cwd(),n=Date.now(),r={terminalId:t,sessionId:s,shell:i,cwd:o,startedAt:n,lastActiveAt:n,cols:e.cols,rows:e.rows,state:"opening",nextSeq:1,output:[],process:null};this.sessions.set(t,r);try{r.process=createDefaultProcess({shell:i,cwd:o,cols:e.cols,rows:e.rows,onData:e=>this.handleProcessData(t,e),onExit:e=>this.handleProcessExit(t,e)}),this.transitionState(r,"running"),this.scheduleIdleTimeout(r),this.scheduleMaxDuration(r)}catch(e){throw this.clearTimers(r),this.transitionState(r,"closed"),this.sessions.delete(t),e}return this.snapshot(r)}writeToSession(e,t){const s=this.requireSession(e);this.assertRunning(s),s.process?.write(t),this.markActivity(s)}resizeSession(e,t,s){const i=this.requireSession(e);this.assertRunning(i),i.cols=t,i.rows=s,i.process?.resize(t,s),this.markActivity(i)}closeSession(e,t){const s=this.sessions.get(e);s&&(t&&!s.timeoutReason&&(s.timeoutReason=t),"closed"!==s.state?(this.clearTimers(s),"closing"!==s.state&&(this.transitionState(s,"closing"),s.process?.close())):this.clearTimers(s))}touchSession(e){const t=this.requireSession(e);this.assertRunning(t),this.markActivity(t)}getSession(e){const t=this.sessions.get(e);return t?this.snapshot(t):void 0}create(e){const t=this.openSession({command:e,cols:80,rows:24}),s=this.sessions.get(t.terminalId);return{id:s.terminalId,command:s.shell,state:s.state,output:s.output,exitCode:s.exitCode,seq:s.nextSeq-1}}get(e){const t=this.sessions.get(e);if(t)return{id:t.terminalId,state:t.state,output:t.output,exitCode:t.exitCode,seq:t.nextSeq-1}}write(e,t){try{return this.writeToSession(e,Buffer.from(t,"utf8")),!0}catch{return!1}}resize(e,t,s){try{return this.resizeSession(e,t,s),!0}catch{return!1}}kill(e,t="SIGTERM"){const s=this.sessions.get(e);s&&s.process&&("closing"!==s.state&&this.transitionState(s,"closing"),s.process.close())}release(e){const t=this.sessions.get(e);t&&(!t.process||"running"!==t.state&&"opening"!==t.state||this.kill(e),this.clearTimers(t),this.sessions.delete(e))}listSessions(){return[...this.sessions.values()].map(e=>this.snapshot(e))}get sessionCount(){return this.sessions.size}get maxConcurrentLimit(){return this.maxConcurrent}destroy(){for(const e of[...this.sessions.keys()])this.release(e)}snapshot(e){return{terminalId:e.terminalId,sessionId:e.sessionId,shell:e.shell,cwd:e.cwd,startedAt:e.startedAt,lastActiveAt:e.lastActiveAt,cols:e.cols,rows:e.rows,state:e.state}}requireSession(e){const t=this.sessions.get(e);if(!t)throw new TerminalNotFoundError(e);return t}assertRunning(e){if("running"!==e.state)throw new TerminalClosedError(e.terminalId,e.state)}transitionState(e,t){const s=e.state;s!==t&&ALLOWED_STATE_TRANSITIONS[s].includes(t)&&(e.state=t,this.onStateChange?.({terminalId:e.terminalId,sessionId:e.sessionId,from:s,to:t}))}handleProcessData(e,t){const s=this.sessions.get(e);if(!s||"closed"===s.state)return;this.markActivity(s);const i=s.nextSeq++;s.output.push(t.toString("utf-8")),this.onOutput?.({terminalId:s.terminalId,sessionId:s.sessionId,data:t,seq:i})}handleProcessExit(e,t){const s=this.sessions.get(e);s&&(this.clearTimers(s),s.exitCode=t.code??void 0,"closing"!==s.state&&this.transitionState(s,"closing"),this.transitionState(s,"closed"),this.onExit?.({terminalId:s.terminalId,sessionId:s.sessionId,code:t.code,signal:t.signal,...s.timeoutReason?{reason:s.timeoutReason}:{}}))}markActivity(e){e.lastActiveAt=Date.now(),"running"===e.state&&this.refreshIdleTimer(e)}evictOldest(){let e=null;for(const t of this.sessions.values()){if("closed"===t.state){e=t;break}(!e||t.lastActiveAt<e.lastActiveAt||t.lastActiveAt===e.lastActiveAt&&t.startedAt<e.startedAt)&&(e=t)}e&&this.release(e.terminalId)}refreshIdleTimer(e){e.idleTimer&&clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{"running"===e.state&&this.closeSession(e.terminalId,TERMINAL_TIMEOUT_REASONS.idle)},this.idleTimeoutMs),"object"==typeof e.idleTimer&&"unref"in e.idleTimer&&e.idleTimer.unref()}scheduleIdleTimeout(e){this.refreshIdleTimer(e)}scheduleMaxDuration(e){e.maxDurationTimer=setTimeout(()=>{"running"===e.state&&this.closeSession(e.terminalId,TERMINAL_TIMEOUT_REASONS.maxDuration)},this.maxDurationMs),"object"==typeof e.maxDurationTimer&&"unref"in e.maxDurationTimer&&e.maxDurationTimer.unref()}clearTimers(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=void 0),e.maxDurationTimer&&(clearTimeout(e.maxDurationTimer),e.maxDurationTimer=void 0)}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beeos-claw",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
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>",
|