beeos-claw 0.2.16 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID,createHash}from"node:crypto";import{mkdir,writeFile,rm,lstat,readlink}from"node:fs/promises";import{join}from"node:path";import{homedir}from"node:os";import{sanitizePromptPayload,stripTransportMetadata}from"../message-filter.js";import{CANVAS_TERM_MAPPING,CANVAS_UPDATE_HINT,buildActiveSurfacesContext}from"../canvas/tools.js";import{parseMentionsParam,buildMentionInstruction}from"../a2a/mention-resolver.js";import{extractPromptBlocks,toGatewayPrompt,resolveSlashCommandName as resolveSlashCmd}from"./prompt-converter.js";import{loadHistory,groupIntoTurns,extractBeeosFileUrisFromOutput,splitUserReplayBlocks}from"./history-replay.js";import{buildResolutionPlan,resolveResources,applyResolutions}from"./prompt-resource-resolver.js";import{readUploadToolResult}from"../upload/result-cache.js";import{BEEOS_UPLOAD_TOOL_NAME}from"../upload/tool.js";import{buildToolResultSessionContentBlocks}from"./tool-result-payload-strategies.js";import{Errors}from"../errors/index.js";import{formatFriendlyError}from"../errors/classify.js";import{resolveChannelFromSessionKey}from"../channel-detection.js";import{ACP_PROTOCOL_VERSION,DEFAULT_PROMPT_TIMEOUT_MS}from"./types.js";const DEFAULT_MODE={id:"default",name:"Default",description:"Default agent mode"},USER_MESSAGE_PREFIX="User Message From BeeOS:",USER_MESSAGE_PREFIX_WITH_NEWLINE=`${USER_MESSAGE_PREFIX}\n`,SLASH_COMMAND_NAME_RE=/^[a-z0-9_-]+$/i;function withUserMessagePrefix(e){return e.startsWith(USER_MESSAGE_PREFIX)?e:`${USER_MESSAGE_PREFIX_WITH_NEWLINE}${e}`}const BACKGROUND_SESSION_PATTERNS=[":cron:",":hook:","node-",":a2a:"];function isBackgroundSessionKey(e){return BACKGROUND_SESSION_PATTERNS.some(s=>e.includes(s))}function isSystemDefaultSessionKey(e){const s=e.split(":");if(s.length<3||"agent"!==s[0])return!1;const t=s.slice(2).join(":");return"main"===t||t===s[1]}function cleanGatewayDerivedTitle(e){if(!e)return;let s=stripTransportMetadata(e);return/^\[System\b/.test(s)?void 0:(s=s.trim(),s||void 0)}function resolveStandaloneSlashCommandName(e,s){if(0===e.length)return;if(e.some(e=>"text"!==e.type))return;const t=s.trim();if(!t||t.includes("\n")||!t.startsWith("/"))return;const n=t.match(/^\/([^\s:]+)(?::|\s|$)/)?.[1]?.trim();return n&&!n.includes("/")&&SLASH_COMMAND_NAME_RE.test(n)?n.toLowerCase():void 0}function deriveSessionId(e,s){const t=createHash("sha256").update(`${s}:${e}`).digest("hex").substring(0,32);return`session:${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20,32)}`}function parseAgentIdFromKey(e){const s=e.split(":");return(s.length>=3&&"agent"===s[0]?s[1]:"")||"main"}function createSessionObject(e){const s=parseAgentIdFromKey(e.gatewayKey);return{sessionId:deriveSessionId(e.gatewayKey,e.instanceId),gatewaySessionKey:e.gatewayKey,agentId:s,platformAgentId:e.resolvePlatformAgentId?.(s),createdAt:Date.now(),chatReplayDedupKeys:new Set,cwd:e.cwd,title:e.title,channelType:resolveChannelFromSessionKey(e.gatewayKey,e.thirdPartyChannels)}}export class AcpGatewayBridgeCore{state;transport;config;logger;writeObs;getGateway;thirdPartyChannels;beeosFileResolver;initialized=!1;agentMapping=null;_syncResolvers=[];_mappingReadyCallbacks=[];setAgentMapping(e){this.agentMapping=e;for(const s of this.state.sessions.values())!s.platformAgentId&&s.agentId&&(s.platformAgentId=e.get(s.agentId)?.id);for(const e of this._syncResolvers)e();this._syncResolvers=[];for(const e of this._mappingReadyCallbacks)e();this._mappingReadyCallbacks=[]}onceAgentMappingReady(e){this.agentMapping&&this.agentMapping.size>0?e():this._mappingReadyCallbacks.push(e)}markAgentSyncDone(){for(const e of this._syncResolvers)e();this._syncResolvers=[]}waitForAgentMapping(e=15e3){return this.agentMapping?Promise.resolve():new Promise(s=>{this._syncResolvers.push(s);const t=setTimeout(()=>{const e=this._syncResolvers.indexOf(s);-1!==e&&this._syncResolvers.splice(e,1),s()},e);"object"==typeof t&&"unref"in t&&t.unref()})}handleTerminalRequest=null;handleTerminalNotification=null;onRunComplete=null;onRunFailed=null;onRunEnded=null;handleSlashCommand=null;onPromptStart=null;instanceId;constructor(e){this.state=e.state,this.transport=e.transport,this.config=e.config,this.logger=e.logger,this.writeObs=e.writeObs,this.getGateway=e.getGateway,this.instanceId=e.instanceId||"unknown",this.thirdPartyChannels=e.thirdPartyChannels??new Set,this.beeosFileResolver=e.beeosFileResolver}resolvePlatformAgentId(e){return this.agentMapping?.get(e)?.id}getDefaultAgentId(){return this.agentMapping&&this.agentMapping.size>0?this.agentMapping.keys().next().value??"main":"main"}registerSession(e){this.state.sessions.set(e.sessionId,e),this.state.sessionsByGatewayKey.set(e.gatewaySessionKey,e)}ensureSessionForKey(e){const s=this.state.findSessionByKey(e);if(s)return s;const t=createSessionObject({gatewayKey:e,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels});return this.registerSession(t),this.logger.debug?.(`[bridge-core] ensureSessionForKey: lazily created session sid=${t.sessionId} key=${e}`),t}async preloadSessionsFromGateway(){const e=this.getGateway();if(e?.isConnected)try{const s=await e.sessionsList();if(!s||"object"!=typeof s)return;const t=s.sessions;if(!Array.isArray(t))return;let n=0;for(const e of t){const s=e,t=s.sessionKey??s.key??s.id??s.name;if(!t||"string"!=typeof t)continue;if(this.state.findSessionByKey(t))continue;const i=s.label||s.derivedTitle||s.title||void 0,o=createSessionObject({gatewayKey:t,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,title:i||"New chat"});this.registerSession(o),n++}n>0&&this.logger.info?.(`[bridge-core] preloadSessions: loaded ${n} session(s) from gateway`)}catch(e){this.logger.debug?.("[bridge-core] preloadSessions failed:",e)}else this.logger.debug?.("[bridge-core] preloadSessions skipped: gateway not connected")}async handleRequest(e){this.writeObs({component:"bridge-core",domain:"forward",name:`rpc.request.${e.method}`,severity:"debug",payload:{id:e.id,method:e.method}});try{switch(e.method){case"initialize":this.handleInitialize(e);break;case"session/new":await this.handleSessionNew(e);break;case"session/load":await this.handleSessionLoad(e);break;case"session/list":await this.handleSessionList(e);break;case"session/rename":this.handleSessionRename(e);break;case"session/delete":await this.handleSessionDelete(e);break;case"session/clear":await this.handleSessionClear(e);break;case"session/prompt":await this.handleSessionPrompt(e);break;case"session/set_mode":await this.handleSessionSetMode(e);break;case"session/set_model":await this.handleSessionSetModel(e);break;case"skills/list":await this.handleSkillsList(e);break;case"skills/install":await this.handleSkillsInstall(e);break;case"skills/uninstall":await this.handleSkillsUninstall(e);break;case"skills/update":await this.handleSkillsUpdate(e);break;case"models/list":await this.handleModelsList(e);break;case"session/cancel":this.handleSessionCancel(e.id,e.params);break;case"cron/list":case"cron/status":case"cron/add":case"cron/update":case"cron/remove":case"cron/run":case"cron/runs":await this.handleCronRequest(e);break;default:e.method.startsWith("terminal/")&&this.handleTerminalRequest?await this.handleTerminalRequest(e):this.transport.sendAcpError(e.id,Errors.MethodNotFound,{message:`Method not found: ${e.method}`})}}catch(s){this.transport.sendAcpError(e.id,Errors.InternalError,{message:s instanceof Error?s.message:"Internal error"})}}handleCanvasNotification=null;getCanvasState=null;onSessionLoaded=null;onSessionDeleted=null;onSessionCleared=null;handleNotification(e){switch(e.method){case"session/cancel":this.handleSessionCancel(void 0,e.params);break;case"canvas/toggle":case"canvas/clear":this.handleCanvasNotification?this.handleCanvasNotification(e.method,e.params):this.logger.debug?.(`[bridge-core] Canvas notification ignored (canvas disabled): ${e.method}`);break;case"canvas/reference":{const s=e.params,t=s?.surfaceId,n=this.state.resolveSessionFromParams(s);if(!n){this.logger.warn?.(`[bridge-core] canvas/reference ignored — no session found for sessionId=${s?.sessionId}`);break}n.referencedSurfaceId=t||void 0,this.logger.debug?.(`[bridge-core] Canvas reference ${t?"set":"cleared"}: ${t??"(none)"}`);break}default:e.method.startsWith("terminal/")&&this.handleTerminalNotification?this.handleTerminalNotification(e.method,e.params):this.logger.debug?.(`[bridge-core] Unknown notification: ${e.method}`)}}handleInitialize(e){this.initialized=!0;const s=this.transport.forwardControl;!1===e.params?.forwardThinking&&(s.forwardThinking=!1),!1===e.params?.forwardToolCalls&&(s.forwardToolCalls=!1),this.transport.sendResult(e.id,{protocolVersion:ACP_PROTOCOL_VERSION,agentCapabilities:{loadSession:!0,skills:!0,promptCapabilities:{image:!0,audio:!1,embeddedContext:!0},sessionCapabilities:{list:{},"web-ssh":!!this.config.bridge.shell.enabled,canvas:!!this.config.canvasEnabled,cron:!0},mcp:{}},agentInfo:{name:"beeos-claw",title:"BeeOS Agent Bridge",version:"0.1.0"},_meta:{},authMethods:[]})}async handleSessionNew(e){const s=`agent:${"string"==typeof e.params?.agentId&&e.params.agentId||this.config.gateway.agentId||this.getDefaultAgentId()}:${randomUUID()}`,t="string"==typeof e.params?.cwd?e.params.cwd:".",n=createSessionObject({gatewayKey:s,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,cwd:t});this.registerSession(n),this.transport.sendResult(e.id,{sessionId:n.sessionId,modes:{availableModes:[{...DEFAULT_MODE}],currentModeId:DEFAULT_MODE.id},_meta:{sessionKey:s,agentId:n.agentId,platformAgentId:n.platformAgentId??null}}),this.fetchHistoryInBackground(s)}fetchHistoryInBackground(e){const s=this.getGateway();s?.isConnected&&loadHistory(s,e,50).catch(e=>{this.logger.debug?.(`[bridge-core] Background history fetch failed: ${e instanceof Error?e.message:e}`)})}async recoverSessionFromGateway(e){const s=this.getGateway();if(s?.isConnected)try{const t=await s.sessionsList();if(!t||"object"!=typeof t)return;const n=t.sessions;if(!Array.isArray(n))return;for(const s of n){const t=s,n=t.sessionKey??t.key??t.id??t.name;if(n&&"string"==typeof n&&deriveSessionId(n,this.instanceId)===e){const e=createSessionObject({gatewayKey:n,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels});return this.registerSession(e),e}}}catch(e){this.logger.debug?.("[bridge-core] recoverSessionFromGateway failed:",e)}}async resolveOrCreateSession(e){let s=this.state.sessions.get(e);if(!s&&e.startsWith("agent:")&&(s=this.state.findSessionByKey(e)),s||e.startsWith("agent:")||(s=await this.recoverSessionFromGateway(e)),s)return{session:s,created:!1};const t=e.startsWith("agent:"),n=this.config.gateway.agentId||this.getDefaultAgentId();return s=createSessionObject({gatewayKey:t?e:`agent:${n}:${e}`,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels}),this.registerSession(s),{session:s,created:!0}}async handleSessionLoad(e){const s=e.params?.sessionId;if(!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const{session:t}=await this.resolveOrCreateSession(s),n=t.sessionId;this.state.lastActiveSessionId=n,t.chatReplayDedupKeys.clear();let i=[];try{const e=this.getGateway(),s=await loadHistory(e,t.gatewaySessionKey,50);i=groupIntoTurns(s);const o=i.filter(e=>"user"===e.role).length,r=i.filter(e=>"assistant"===e.role).length,a=i.filter(e=>e.toolCalls?.length).length,d=i.reduce((e,s)=>e+(s.toolCalls?.length??0),0);this.logger.debug?.(`[bridge-core] handleSessionLoad sid=${n} messages=${s.length} turns=${i.length} userTurns=${o} asstTurns=${r} turnsWithToolCalls=${a} totalToolCalls=${d}`);for(let e=0;e<i.length;e++){const s=i[e];if("user"!==s.role||"internal_system"!==s.provenance?.kind)if("system"!==s.role){if(this.logger.debug?.(`[bridge-core] SENDING history_turn[${e}] role=${s.role} contentLen=${s.content.length} content=${JSON.stringify(s.content.slice(0,80))} tcCount=${s.toolCalls?.length??0}`),"user"===s.role)this.replayUserTurnAsChunks(n,s.content,s.metadata);else{const e={sessionUpdate:"history_turn",role:s.role,content:s.content,reasoning:s.reasoning,toolCalls:s.toolCalls};s.metadata&&(e.metadata=s.metadata),this.transport.sendSessionUpdate(n,e)}if("assistant"===s.role){if(s.files?.length)for(const e of s.files)this.transport.sendSessionUpdate(n,{sessionUpdate:"agent_message_chunk",content:{type:"resource_link",uri:e.uri,...e.name?{name:e.name}:{},...e.mimeType?{mimeType:e.mimeType}:{}}});s.toolCalls?.length&&await this.replayUploadFileBlocks(n,s.toolCalls)}}else this.logger.debug?.(`[bridge-core] SENDING system_message[${e}] contentLen=${s.content.length}`),this.transport.sendSessionUpdate(n,{sessionUpdate:"system_message_chunk",content:{type:"text",text:s.content},metadata:s.metadata});else this.logger.debug?.(`[bridge-core] SKIP history_turn[${e}] internal_system user message`)}}catch(e){this.logger.warn?.("[bridge-core] Failed to load session history:",e)}const o=new Set;for(const e of i)if(e.toolCalls)for(const s of e.toolCalls)s.id&&o.add(s.id),s.callId&&s.callId!==s.id&&o.add(s.callId);if(void 0!==t.pendingPromptId&&!t.promptDone&&t.assistantMq&&!t.assistantMq.closed)try{this.replayCurrentAssistantStream(t,o)}catch(e){this.logger.warn?.(`[bridge-core] replayCurrentAssistantStream failed: ${e instanceof Error?e.message:e}`)}(()=>{this.transport.sendResult(e.id,{sessionId:n}),this.onSessionLoaded?.(n,t.gatewaySessionKey,t.platformAgentId),this.pushSkillsList().catch(e=>{this.logger.warn?.(`[bridge-core] pushSkillsList failed: ${e instanceof Error?e.message:e}`)})})()}async replayUploadFileBlocks(e,s){for(const t of s){if(t.name!==BEEOS_UPLOAD_TOOL_NAME)continue;let s=[];try{const e=await readUploadToolResult(t.id);e&&(s=buildToolResultSessionContentBlocks(t.name,e))}catch{}if(0===s.length){const e=extractBeeosFileUrisFromOutput(t.output);for(const t of e)s.push({type:"content",content:{type:"resource_link",uri:t,name:"file"}})}const n=s.filter(e=>"resource_link"===e.content?.type);for(const s of n)this.transport.sendSessionUpdate(e,{sessionUpdate:"agent_message_chunk",content:s.content});n.length>0&&this.logger.debug?.(`[bridge-core] replayUploadFileBlocks tcId=${t.id} sent ${n.length} resource_link block(s)`)}}replayCurrentAssistantStream(e,s){if(!e.assistantMq||e.assistantMq.closed)return;const t=e.assistantMq.peek(0);if(0===t.length)return;let n=0,i=0;for(const o of t)s?.size&&o.toolCallId&&s.has(o.toolCallId)&&("tool_call"===o.sessionUpdate||"tool_call_update"===o.sessionUpdate)?i++:(this.transport.sendSessionUpdate(e.sessionId,o),n++);i>0&&this.logger.debug?.(`[bridge-core] replayCurrentAssistantStream skipped ${i} duplicate tool events (already in history)`),n>0&&this.writeObs({component:"bridge-core",domain:"lifecycle",name:"session.load.replay_complete",severity:"debug",payload:{sessionId:e.sessionId,replayed:n}})}async handleSessionList(e){const s=[];for(const[,e]of this.state.sessions)isBackgroundSessionKey(e.gatewaySessionKey)||s.push({sessionId:e.sessionId,title:e.title||"New chat",cwd:e.cwd,updatedAt:e.lastStreamActivityTs??e.createdAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(e.gatewaySessionKey)});const t=this.getGateway();if(t?.isConnected)try{const e=await t.sessionsList();if(e&&"object"==typeof e&&Array.isArray(e.sessions)){const t=e.sessions;for(const e of t){const t=e,n=t.sessionKey??t.key??t.id??t.name;if(!n||"string"!=typeof n)continue;if(isBackgroundSessionKey(n))continue;const i=deriveSessionId(n,this.instanceId),o=s.find(e=>e.sessionId===n||e.sessionId===i||this.state.sessions.get(e.sessionId)?.gatewaySessionKey===n),r=t.label||cleanGatewayDerivedTitle(t.derivedTitle)||t.title||void 0,a=t.model,d=t.modelProvider;if(o){if(!o.title||"New chat"===o.title){const e=r||o.sessionId;o.title=e;const s=this.state.sessions.get(o.sessionId);s&&!s.title&&(s.title=e)}const e=t.updatedAt;e&&(o.updatedAt=e),a&&(o.model=a),d&&(o.modelProvider=d)}else{const e=createSessionObject({gatewayKey:n,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,cwd:t.cwd,title:r||"New chat"});this.registerSession(e),s.push({sessionId:e.sessionId,title:r||"New chat",cwd:t.cwd,updatedAt:t.updatedAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(n),model:a,modelProvider:d})}}}}catch(e){this.logger.debug?.("[bridge-core] sessions.list enrichment failed:",e)}s.sort((e,s)=>(s.updatedAt??0)-(e.updatedAt??0)||e.sessionId.localeCompare(s.sessionId)),this.transport.sendResult(e.id,{sessions:s})}handleSessionRename(e){const s=e.params?.sessionId,t=e.params?.title;if(!s||!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, title"});const n=t.slice(0,64),i=this.state.sessions.get(s);if(!i)return void this.transport.sendAcpError(e.id,Errors.SessionNotFound);i.title=n;const o=i.gatewaySessionKey,r=this.getGateway();r?.isConnected&&r.sessionsPatch(o,{label:n}).catch(e=>{this.logger.debug?.(`[bridge-core] sessionsPatch (rename) failed: ${e instanceof Error?e.message:e}`)}),this.transport.sendResult(e.id,{sessionId:s,title:n})}async handleSessionDelete(e){const s=e.params?.sessionId;if(!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const t=this.state.sessions.get(s);if(!t)return this.logger.info?.(`[bridge-core] session/delete sessionId=${s} NOT FOUND in local state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);const n=t.gatewaySessionKey;this.logger.info?.(`[bridge-core] session/delete sessionId=${s} gatewayKey=${n} gwConnected=${!!this.getGateway()?.isConnected}`);let i=!1;const o=this.getGateway();if(o?.isConnected&&n)try{await o.sessionsDelete(n),i=!0,this.logger.info?.(`[bridge-core] Gateway sessions.delete SUCCESS for key=${n}`)}catch(s){const t=s instanceof Error?s.message:String(s);if(this.logger.info?.(`[bridge-core] Gateway sessions.delete FAILED for key=${n}: ${t}`),!t.includes("Cannot delete the main session"))return void this.transport.sendResult(e.id,{deleted:!1});try{await o.sessionsReset(n),i=!0,this.logger.info?.(`[bridge-core] Gateway sessions.reset SUCCESS for key=${n}`)}catch(s){return this.logger.warn?.(`[bridge-core] Gateway sessions.reset also failed for key=${n}: ${s instanceof Error?s.message:s}`),void this.transport.sendResult(e.id,{deleted:!1})}}else n&&this.logger.warn?.(`[bridge-core] Gateway not connected, proceeding with local-only delete for key=${n}`);void 0!==t.pendingPromptId&&this.failPrompt(t,Errors.SessionDeleted.rpcCode,Errors.SessionDeleted.message),this.state.clearPromptTimeout(t),this.state.clearStreamIdleTimer(t),t.assistantMq?.close(),t.currentRunId&&this.state.promptsByRunId.delete(t.currentRunId);const r=new Set;for(const[e,t]of this.state.toolCallToSessionMap)t===s&&(this.state.toolCallToSessionMap.delete(e),r.add(e));for(const[e,s]of this.state.toolCallIdMap)r.has(s)&&this.state.toolCallIdMap.delete(e);this.state.sessions.delete(s),this.state.sessionsByGatewayKey.delete(n),this.transport.resetSessionUpdateCount(s),i&&this.onSessionDeleted?.(n),this.logger.info?.(`[bridge-core] session/delete DONE sessionId=${s} gatewayKey=${n} deleted=true`),this.transport.sendResult(e.id,{deleted:!0})}async handleSessionClear(e){this.logger.info?.(`[bridge-core] handleSessionClear called, params=${JSON.stringify(e.params)}`);const s=e.params?.sessionId;if(!s)return this.logger.warn?.("[bridge-core] session/clear missing sessionId"),void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const t=this.state.sessions.get(s);if(!t)return this.logger.warn?.(`[bridge-core] session/clear sessionId=${s} not found in state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);this.logger.info?.(`[bridge-core] session/clear sessionId=${s} gatewayKey=${t.gatewaySessionKey}`);const n=this.getGateway();if(n?.isConnected&&t.gatewaySessionKey)try{await n.sessionsReset(t.gatewaySessionKey),this.logger.info?.(`[bridge-core] Gateway sessions.reset SUCCESS for key=${t.gatewaySessionKey}`)}catch(e){this.logger.warn?.(`[bridge-core] Gateway sessions.reset FAILED for key=${t.gatewaySessionKey}: ${e instanceof Error?e.message:e}`)}else this.logger.warn?.(`[bridge-core] session/clear skipped gateway reset: connected=${n?.isConnected}, key=${t.gatewaySessionKey}`);t.chatReplayDedupKeys.clear(),this.logger.info?.("[bridge-core] session/clear dedupKeys cleared, calling onSessionCleared"),this.onSessionCleared?.(s,t.gatewaySessionKey),this.transport.sendResult(e.id,{cleared:!0}),this.logger.info?.("[bridge-core] session/clear DONE, sent cleared=true")}async handleSessionSetMode(e){const s=e.params?.sessionId,t=e.params?.mode,n=s?this.state.sessions.get(s):void 0,i=this.getGateway();if(n&&i?.isConnected&&t){const e={};if("verbose"===t||"detailed"===t?e.verboseLevel="full":"concise"!==t&&"default"!==t||(e.verboseLevel="default"),"reasoning"!==t&&"thinking"!==t||(e.reasoningLevel="stream"),Object.keys(e).length>0)try{await i.sessionsPatch(n.gatewaySessionKey,e)}catch(e){this.logger.warn?.(`[bridge-core] sessions.patch failed: ${e instanceof Error?e.message:e}`)}}this.transport.sendResult(e.id,{mode:t??"default"})}async handleSessionSetModel(e){const s=e.params?.sessionId,t=e.params?.model,n=s?this.state.sessions.get(s):void 0,i=this.getGateway();let o=t??null,r=null;if(n&&i?.isConnected&&t)try{const e=await i.sessionsPatch(n.gatewaySessionKey,{model:t}),s=e?.resolved;s?.model&&(o=s.model),s?.modelProvider&&(r=s.modelProvider)}catch(e){this.logger.warn?.(`[bridge-core] sessions.patch for model failed: ${e instanceof Error?e.message:e}`)}this.transport.sendResult(e.id,{model:o,modelProvider:r})}async handleModelsList(e){const s=this.getGateway();if(s?.isConnected)try{const t=await s.request("models.list",{});this.transport.sendResult(e.id,t??{models:[]})}catch(s){this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Failed to list models: ${s instanceof Error?s.message:s}`})}else this.transport.sendAcpError(e.id,Errors.GatewayNotConnected)}async handleSkillsList(e){const s=await this.fetchSimplifiedSkills();this.transport.sendResult(e.id,{skills:s})}async fetchSimplifiedSkills(){const e=this.getGateway();if(!e?.isConnected)return[];try{const s=await e.skillsStatus();return s?.skills?s.skills.filter(e=>!1!==e.eligible||!0===e.disabled).sort((e,s)=>e.always&&!s.always?-1:!e.always&&s.always?1:e.name.localeCompare(s.name)).map(e=>({name:e.name,description:e.description,emoji:e.emoji,always:e.always||void 0,bundled:e.bundled||void 0,disabled:e.disabled||void 0,skillKey:e.skillKey||void 0})):[]}catch(e){return this.logger.warn?.(`[bridge-core] Failed to fetch skills: ${e instanceof Error?e.message:e}`),[]}}async pushSkillsList(){const e=await this.fetchSimplifiedSkills();this.transport.sendNotification("session/update",{sessionId:"_system",update:{sessionUpdate:"available_commands_update",commands:e}}),this.writeObs({component:"bridge-core",domain:"lifecycle",name:"skills.pushed",severity:"debug",payload:{count:e.length}})}getSkillsDir(){const e=process.env.OPENCLAW_HOME||join(homedir(),".openclaw");return join(e,"skills")}async handleSkillsInstall(e){const s=e.params,t=s?.name||s?.slug,n=s?.files;if(!t||!n?.length)return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skill name or files"});const i=join(this.getSkillsDir(),t);try{await mkdir(i,{recursive:!0});for(const e of n){const s=join(i,e.path);await mkdir(join(s,".."),{recursive:!0});const t=await fetch(e.downloadUrl,{signal:AbortSignal.timeout(12e4)});if(!t.ok)throw new Error(`Failed to download ${e.path}: ${t.status}`);const n=Buffer.from(await t.arrayBuffer());await writeFile(s,n)}this.logger.info?.(`[bridge-core] Installed skill "${t}" (${n.length} files)`),this.transport.sendResult(e.id,{success:!0}),await this.pushSkillsList()}catch(s){this.logger.error?.(`[bridge-core] Failed to install skill "${t}": ${s instanceof Error?s.message:s}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Install failed: ${s instanceof Error?s.message:String(s)}`})}}async handleSkillsUninstall(e){const s=e.params,t=s?.name;if(!t)return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skill name"});const n=join(this.getSkillsDir(),t),i=join(homedir(),".agents","skills",t);try{let s=!1;try{const e=await lstat(n);if(e.isSymbolicLink()){const e=await readlink(n);await rm(n,{force:!0}),await rm(e,{recursive:!0,force:!0}),s=!0}else e.isDirectory()&&(await rm(n,{recursive:!0,force:!0}),s=!0)}catch{}try{const e=await lstat(i);(e.isDirectory()||e.isSymbolicLink())&&(await rm(i,{recursive:!0,force:!0}),s=!0)}catch{}s||this.logger.warn?.(`[bridge-core] Skill "${t}" not found in any known location`),this.logger.info?.(`[bridge-core] Uninstalled skill "${t}"`),this.transport.sendResult(e.id,{success:!0}),await this.pushSkillsList()}catch(s){this.logger.error?.(`[bridge-core] Failed to uninstall skill "${t}": ${s instanceof Error?s.message:s}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Uninstall failed: ${s instanceof Error?s.message:String(s)}`})}}async handleSkillsUpdate(e){const s=e.params,t=s?.skillKey,n=s?.enabled;if(!t||"boolean"!=typeof n)return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skillKey or enabled"});const i=this.getGateway();if(i?.isConnected)try{const s=await i.skillsUpdate(t,n);this.logger.info?.(`[bridge-core] Updated skill "${t}" enabled=${n}`),this.transport.sendResult(e.id,{ok:s?.ok??!0,skillKey:t,enabled:n}),await this.pushSkillsList()}catch(s){this.logger.error?.(`[bridge-core] Failed to update skill "${t}": ${s instanceof Error?s.message:s}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Update failed: ${s instanceof Error?s.message:String(s)}`})}else this.transport.sendAcpError(e.id,Errors.InternalError,{message:"Gateway not connected"})}async handleSessionPrompt(e){const s=e.params?.sessionId,t=e.params?.prompt;if(!s||!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, prompt"});const{session:n,created:i}=await this.resolveOrCreateSession(s);i&&this.onSessionLoaded?.(n.sessionId,n.gatewaySessionKey,n.platformAgentId);const o=n.sessionId;this.state.lastActiveSessionId=o,this.onPromptStart?.();const r=this.getGateway();if(!r?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);let a=extractPromptBlocks(sanitizePromptPayload(t));if(!n.title){const e=a.find(e=>"text"===e.type&&e.text);e&&"text"in e&&e.text&&(n.title=e.text.slice(0,64).replace(/\n/g," ").trim(),r?.isConnected&&n.title&&r.sessionsPatch(n.gatewaySessionKey,{label:n.title}).catch(e=>{this.logger.debug?.(`[bridge-core] sessionsPatch (title) failed: ${e instanceof Error?e.message:e}`)}))}const d=buildResolutionPlan(a);if(d.refs.length>0)try{const e=await resolveResources(d,this.writeObs);a=applyResolutions(a,e)}catch(e){this.logger.warn?.(`[bridge-core] Resource pre-resolution failed: ${e instanceof Error?e.message:e}`)}let l;if(this.beeosFileResolver){const e=this.beeosFileResolver.buildResolutionPlan(a);if(e.refs.length>0)try{l=await this.beeosFileResolver.resolveMetadataForPrompt(e)}catch(e){this.logger.warn?.(`[bridge-core] BeeOS file resolution failed: ${e instanceof Error?e.message:e}`)}}const c=toGatewayPrompt(a,{beeosFileResolutionsByUri:l});if("error"in c)return void this.transport.sendAcpError(e.id,Errors.PromptConvertFailed,{message:c.error});const{message:p,attachments:h}=c,g=resolveStandaloneSlashCommandName(a,p)??resolveSlashCmd(a,p),m="string"==typeof g;if(g&&this.handleSlashCommand){const s=this;if(await(async()=>{try{return await s.handleSlashCommand(g)}catch(e){return s.logger.warn?.(`[bridge-core] /${g} handler failed: ${e instanceof Error?e.message:e}`),!1}})())return this.emitPromptAsUserSessionUpdates(n.sessionId,a),this.transport.sendSessionUpdate(o,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:`Loading ${g}...`}}),void this.transport.sendResult(e.id,{stopReason:"end_turn"})}const u="chat.send";let y=m?p:withUserMessagePrefix(p);const f=this.config.canvasEnabled&&!m&&!1!==n.canvasActive&&!n.channelType;if(f){const e=n.promptsSinceCanvasInjection??0;!n.canvasPromptInjected||e>=10?(y=`${CANVAS_TERM_MAPPING}\n\n${y}`,n.canvasPromptInjected=!0,n.promptsSinceCanvasInjection=0,this.logger.debug?.("[bridge-core] Canvas term mapping injected (re-inject cycle)")):n.promptsSinceCanvasInjection=e+1}if(f){const e=this.getCanvasState?.();if(e){const s=e.resolveCanvasForSession(n.gatewaySessionKey);y=`${buildActiveSurfacesContext(e,s)??"[Canvas] Canvas panel is available. Use render_ui for structured/visual content."}\n\n${y}`}const s=n.referencedSurfaceId;s&&(y=`${CANVAS_UPDATE_HINT}\n[IMPORTANT: The user selected canvas surface "${s}" for editing. You MUST call render_ui with id="${s}" to UPDATE this existing surface. Do NOT create a new surface with a different id.]\n${y}`,n.referencedSurfaceId=void 0)}if(!m){const s=parseMentionsParam(e.params?.mentions);if(s.length>0){n.activeMentions=s;y=`${buildMentionInstruction(s)}\n${y}`,this.logger.debug?.(`[bridge-core] A2A mention instruction injected for ${s.length} agent(s)`)}else n.activeMentions=void 0}m&&this.logger.info?.(`[bridge-core] slash command passthrough sessionId=${o} command=/${g}`),n.promptDone||void 0===n.pendingPromptId&&void 0===n.currentRunId||(this.logger.warn?.(`[bridge-core] handleSessionPrompt: force-ending stale run sid=${o} oldRunId=${n.currentRunId} oldPromptId=${n.pendingPromptId}`),(n.currentRunId||n.gatewayRequestId)&&r.agentCancel(n.gatewaySessionKey,{runId:n.currentRunId,requestId:n.gatewayRequestId}),this.endRun(n,{kind:"complete",stopReason:"cancelled"})),this.state.resetForNewRun(n,o,this.transport),n.pendingPromptId=e.id,n.gatewayMethod=u,this.emitPromptAsUserSessionUpdates(o,a),this.transport.sendSessionUpdate(o,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:""}}),this.schedulePromptTimeout(n);const S=h.length>0?h.map(e=>({type:e.type,content:e.content,...e.mimeType?{mimeType:e.mimeType}:{},...e.fileName?{fileName:e.fileName}:{}})):void 0,I=`req_${randomUUID().replace(/-/g,"")}`,w={sessionKey:n.gatewaySessionKey,message:y,deliver:!1,idempotencyKey:randomUUID()};S?.length&&(w.attachments=S);if(!r.sendFrame({type:"req",id:I,method:u,params:w}))return this.state.clearPromptTimeout(n),n.pendingPromptId=void 0,n.assistantMq?.close(),void this.transport.sendAcpError(e.id,Errors.SendFailed,{message:"failed to send prompt to gateway"});n.gatewayRequestId=I,this.state.promptsByGatewayRequestId.set(I,n)}replayUserTurnAsChunks(e,s,t){const n=splitUserReplayBlocks(s);for(const s of n){let n;if("text"===s.type){if(!s.text)continue;n={type:"text",text:s.text}}else n="resource_link"===s.type?{type:"resource_link",uri:s.uri??"",...s.fileName?{name:s.fileName}:{},...s.mimeType?{mimeType:s.mimeType}:{}}:{type:s.type,...s.mimeType?{mimeType:s.mimeType}:{},...s.fileName?{fileName:s.fileName}:{}};this.transport.sendSessionUpdate(e,{sessionUpdate:"user_message_chunk",content:n,...t?{metadata:t}:{}})}}emitPromptAsUserSessionUpdates(e,s){for(const t of s){const s=this.toUserSessionUpdateContent(t);s&&this.transport.sendSessionUpdate(e,{sessionUpdate:"user_message_chunk",content:s})}}toUserSessionUpdateContent(e){if("text"===e.type){if(!e.text)return;return{type:"text",text:e.text}}if("image"===e.type){const s=e.mimeType??e.mime_type??"application/octet-stream",t=e.fileName??e.file_name??e.name;if(!e.data&&!e.uri)return;return{type:"image",...e.data?{data:e.data}:{},...e.uri?{uri:e.uri}:{},...s?{mimeType:s}:{},...t?{fileName:t}:{}}}if("resource_link"===e.type){if(!e.uri)return;return{type:"resource_link",uri:e.uri,...e.title?{title:e.title}:{},...e.name?{name:e.name}:{},...e.mimeType??e.mime_type?{mimeType:e.mimeType??e.mime_type}:{}}}if("file"===e.type){const s=e.mimeType??e.mime_type??"application/octet-stream",t=e.fileName??e.file_name??e.filename??e.name;if(!e.data&&!e.text&&!e.uri)return;return{type:"file",...e.data?{data:e.data}:{},...e.text?{text:e.text}:{},...e.uri?{uri:e.uri}:{},...s?{mimeType:s}:{},...t?{fileName:t}:{}}}if("resource"===e.type){const s=e.resource??{},t=s.uri??e.uri,n=s.mimeType??s.mime_type??e.mimeType??e.mime_type,i=s.text??e.text,o=s.data??e.data,r=s.fileName??s.file_name??s.filename??s.name??e.fileName??e.file_name??e.filename??e.name;if(!t&&!i&&!o)return;return{type:"resource",resource:{...t?{uri:t}:{},...n?{mimeType:n}:{},...i?{text:i}:{},...o?{data:o}:{},...r?{fileName:r}:{}}}}}handleSessionCancel(e,s){const t=s?.sessionId;if(!t)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.MissingParam,{param:"sessionId"}));const n=this.state.sessions.get(t);if(!n)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.SessionNotFound));const i=this.getGateway(),o=i?.isConnected,r=!(!n.currentRunId&&!n.gatewayRequestId);if(o&&r){i.agentCancel(n.gatewaySessionKey,{runId:n.currentRunId,requestId:n.gatewayRequestId})}this.endRun(n,{kind:"complete",stopReason:"cancelled"})||this.transport.sendSessionUpdate(n.sessionId,{sessionUpdate:"stream_complete",stopReason:"cancelled"}),void 0!==e&&this.transport.sendResult(e,{})}endRun(e,s){if(e.promptDone)return this.logger.debug?.(`[bridge-core] endRun skipped (promptDone=true) sessionId=${e.sessionId} outcome=${s.kind}`),!1;if(void 0===e.pendingPromptId&&void 0===e.currentRunId)return this.logger.debug?.(`[bridge-core] endRun skipped (no pendingPromptId and no currentRunId) sessionId=${e.sessionId} outcome=${s.kind}`),!1;e.promptDone=!0;const t=e.currentRunId;if("complete"===s.kind){if(t&&this.onRunComplete){const s=e.agentAssistantTextSoFar??e.chatAssistantTextSoFar??"";this.onRunComplete(t,s)}void 0!==e.pendingPromptId&&this.transport.sendResult(e.pendingPromptId,{stopReason:s.stopReason}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:s.stopReason})}else t&&this.onRunFailed&&this.onRunFailed(t,s.message),void 0!==e.pendingPromptId&&this.transport.sendError(e.pendingPromptId,s.code,s.message),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:"error",error:s.message});return this.cleanupPrompt(e),this.onRunEnded?.(e,s),!0}schedulePromptTimeout(e){this.state.clearPromptTimeout(e);const s=this.config.promptTimeoutMs??DEFAULT_PROMPT_TIMEOUT_MS;s<=0||(e.promptTimeoutTimer=setTimeout(()=>{void 0!==e.pendingPromptId&&(this.logger.warn?.(`[bridge-core] Prompt timed out sessionId=${e.sessionId} after ${s}ms`),this.failPrompt(e,Errors.PromptTimeout.rpcCode,`Prompt timed out after ${s}ms`))},s),"object"==typeof e.promptTimeoutTimer&&"unref"in e.promptTimeoutTimer&&e.promptTimeoutTimer.unref())}cleanupPrompt(e){this.state.clearPromptTimeout(e),this.state.clearStreamIdleTimer(e);const s=e.currentRunId;if(s){this.state.promptsByRunId.delete(s),this.state.markRunCompleted(s);const e=`tc_${s}_`;for(const[s,t]of this.state.toolCallIdMap)t.startsWith(e)&&this.state.toolCallIdMap.delete(s)}e.gatewayRequestId&&this.state.promptsByGatewayRequestId.delete(e.gatewayRequestId),e.pendingPromptId=void 0,e.promptDone=void 0,e.currentRunId=void 0,e.gatewayRequestId=void 0,e.assistantStreamStartedAt=void 0,e.gatewayMethod=void 0,e.assistantMq?.close()}completePrompt(e,s){this.endRun(e,{kind:"complete",stopReason:s})}failPrompt(e,s,t){this.endRun(e,{kind:"error",code:s,message:t})}cleanupAllPendingPrompts(){for(const e of this.state.sessions.values())this.failPrompt(e,Errors.GatewayDisconnected.rpcCode,Errors.GatewayDisconnected.message)}handleGatewayResponse(e){const s=e.id;if(!s)return;const t=this.state.promptsByGatewayRequestId.get(s);if(!t||t.promptDone)return;if(!e.ok){const n=e.error,i=n?.message??"gateway returned error";return this.logger.warn?.(`[bridge-core] handleGatewayResponse: gateway rejected prompt gatewayRequestId=${s} error=${i}`),void this.failPrompt(t,Errors.SendFailed.rpcCode,i)}const n=e.payload,i=n?.runId;i?(t.currentRunId=i,t.assistantStreamStartedAt=Date.now(),this.state.promptsByRunId.set(i,t),this.logger.debug?.(`[bridge-core] handleGatewayResponse: bound runId=${i} gatewayRequestId=${s} sessionId=${t.sessionId}`)):this.logger.debug?.(`[bridge-core] handleGatewayResponse: no runId in payload gatewayRequestId=${s} sessionId=${t.sessionId}`)}maybeCompletePromptFromChatState(e,s){if(e.promptDone||"chat.send"!==e.gatewayMethod)return;const t="string"==typeof s.state?s.state.toLowerCase():void 0;if(t)if("final"===t)this.completePrompt(e,"end_turn");else if("aborted"===t||"cancelled"===t||"cancel"===t)this.completePrompt(e,"cancelled");else if("error"===t){const t=("string"==typeof s.summary?s.summary:void 0)??("string"==typeof s.message?s.message:void 0)??"gateway chat error";this.failPrompt(e,Errors.AgentError.rpcCode,formatFriendlyError(t))}}pushToolResultContent(e,s,t,n){if(0===t.length)return;const i=n?this.state.promptsByRunId.get(n):void 0;if(!i)return void this.logger.warn?.(`[bridge-core] pushToolResultContent dropped — no session for runId=${n??"(none)"}`);const o=i.sessionId;for(const e of t){const s={sessionUpdate:"agent_message_chunk",content:e};this.transport.sendSessionUpdate(o,s),i.assistantMq?.push(s)}this.writeObs({component:"bridge-core",domain:"tool",name:"tool_result.direct_push",severity:"info",payload:{toolCallId:e,toolName:s,blockCount:t.length,sessionId:o}})}async handleCronRequest(e){const s=this.getGateway();if(!s?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);const t=e.method.replace("/",".");try{const n=await s.request(t,e.params??{});this.transport.sendResult(e.id,n??{})}catch(s){const n=s instanceof Error?s.message:"cron request failed";this.logger.warn?.(`[bridge-core] ${t} failed: ${n}`),this.transport.sendAcpError(e.id,Errors.SendFailed,{message:n})}}}
|
|
1
|
+
import{randomUUID,createHash}from"node:crypto";import{mkdir,writeFile,rm,lstat,readlink,stat,readdir,rename,cp}from"node:fs/promises";import{join}from"node:path";import{homedir}from"node:os";import AdmZip from"adm-zip";import{sanitizePromptPayload,stripTransportMetadata,isHeartbeatPromptText,isHeartbeatOnlyText}from"../message-filter.js";import{CANVAS_TERM_MAPPING,CANVAS_UPDATE_HINT,buildActiveSurfacesContext}from"../canvas/tools.js";import{parseMentionsParam,buildMentionInstruction}from"../a2a/mention-resolver.js";import{extractPromptBlocks,toGatewayPrompt,resolveSlashCommandName as resolveSlashCmd}from"./prompt-converter.js";import{loadHistory,groupIntoTurns,extractBeeosFileUrisFromOutput,splitUserReplayBlocks}from"./history-replay.js";import{buildResolutionPlan,resolveResources,applyResolutions}from"./prompt-resource-resolver.js";import{readUploadToolResult}from"../upload/result-cache.js";import{BEEOS_UPLOAD_TOOL_NAME}from"../upload/tool.js";import{buildToolResultSessionContentBlocks}from"./tool-result-payload-strategies.js";import{Errors}from"../errors/index.js";import{formatFriendlyError}from"../errors/classify.js";import{resolveChannelFromSessionKey}from"../channel-detection.js";import{ACP_PROTOCOL_VERSION,DEFAULT_PROMPT_TIMEOUT_MS}from"./types.js";const DEFAULT_MODE={id:"default",name:"Default",description:"Default agent mode"},USER_MESSAGE_PREFIX="User Message From BeeOS:",USER_MESSAGE_PREFIX_WITH_NEWLINE=`${USER_MESSAGE_PREFIX}\n`,SLASH_COMMAND_NAME_RE=/^[a-z0-9_-]+$/i;function withUserMessagePrefix(e){return e.startsWith(USER_MESSAGE_PREFIX)?e:`${USER_MESSAGE_PREFIX_WITH_NEWLINE}${e}`}const BACKGROUND_SESSION_PATTERNS=[":cron:",":hook:","node-",":a2a:"];function isBackgroundSessionKey(e){return BACKGROUND_SESSION_PATTERNS.some(s=>e.includes(s))}function isSystemDefaultSessionKey(e){const s=e.split(":");if(s.length<3||"agent"!==s[0])return!1;const t=s.slice(2).join(":");return"main"===t||t===s[1]}function cleanGatewayDerivedTitle(e){if(!e)return;let s=stripTransportMetadata(e);return/^\[System\b/.test(s)?void 0:(s=s.trim(),s||void 0)}function resolveStandaloneSlashCommandName(e,s){if(0===e.length)return;if(e.some(e=>"text"!==e.type))return;const t=s.trim();if(!t||t.includes("\n")||!t.startsWith("/"))return;const i=t.match(/^\/([^\s:]+)(?::|\s|$)/)?.[1]?.trim();return i&&!i.includes("/")&&SLASH_COMMAND_NAME_RE.test(i)?i.toLowerCase():void 0}function deriveSessionId(e,s){const t=createHash("sha256").update(`${s}:${e}`).digest("hex").substring(0,32);return`session:${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20,32)}`}function parseAgentIdFromKey(e){const s=e.split(":");return(s.length>=3&&"agent"===s[0]?s[1]:"")||"main"}function createSessionObject(e){const s=parseAgentIdFromKey(e.gatewayKey);return{sessionId:deriveSessionId(e.gatewayKey,e.instanceId),gatewaySessionKey:e.gatewayKey,agentId:s,platformAgentId:e.resolvePlatformAgentId?.(s),createdAt:Date.now(),chatReplayDedupKeys:new Set,cwd:e.cwd,title:e.title,channelType:resolveChannelFromSessionKey(e.gatewayKey,e.thirdPartyChannels)}}export class AcpGatewayBridgeCore{state;transport;config;logger;writeObs;getGateway;thirdPartyChannels;beeosFileResolver;initialized=!1;agentMapping=null;_syncResolvers=[];_mappingReadyCallbacks=[];setAgentMapping(e){this.agentMapping=e;for(const s of this.state.sessions.values())!s.platformAgentId&&s.agentId&&(s.platformAgentId=e.get(s.agentId)?.id);for(const e of this._syncResolvers)e();this._syncResolvers=[];for(const e of this._mappingReadyCallbacks)e();this._mappingReadyCallbacks=[]}onceAgentMappingReady(e){this.agentMapping&&this.agentMapping.size>0?e():this._mappingReadyCallbacks.push(e)}markAgentSyncDone(){for(const e of this._syncResolvers)e();this._syncResolvers=[]}waitForAgentMapping(e=15e3){return this.agentMapping?Promise.resolve():new Promise(s=>{this._syncResolvers.push(s);const t=setTimeout(()=>{const e=this._syncResolvers.indexOf(s);-1!==e&&this._syncResolvers.splice(e,1),s()},e);"object"==typeof t&&"unref"in t&&t.unref()})}handleTerminalRequest=null;handleTerminalNotification=null;onRunComplete=null;onRunFailed=null;onRunEnded=null;handleSlashCommand=null;onPromptStart=null;instanceId;constructor(e){this.state=e.state,this.transport=e.transport,this.config=e.config,this.logger=e.logger,this.writeObs=e.writeObs,this.getGateway=e.getGateway,this.instanceId=e.instanceId||"unknown",this.thirdPartyChannels=e.thirdPartyChannels??new Set,this.beeosFileResolver=e.beeosFileResolver}resolvePlatformAgentId(e){return this.agentMapping?.get(e)?.id}getDefaultAgentId(){return this.agentMapping&&this.agentMapping.size>0?this.agentMapping.keys().next().value??"main":"main"}registerSession(e){this.state.sessions.set(e.sessionId,e),this.state.sessionsByGatewayKey.set(e.gatewaySessionKey,e)}ensureSessionForKey(e){const s=this.state.findSessionByKey(e);if(s)return s;const t=createSessionObject({gatewayKey:e,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels});return this.registerSession(t),this.logger.debug?.(`[bridge-core] ensureSessionForKey: lazily created session sid=${t.sessionId} key=${e}`),t}async preloadSessionsFromGateway(){const e=this.getGateway();if(e?.isConnected)try{const s=await e.sessionsList();if(!s||"object"!=typeof s)return;const t=s.sessions;if(!Array.isArray(t))return;let i=0;for(const e of t){const s=e,t=s.sessionKey??s.key??s.id??s.name;if(!t||"string"!=typeof t)continue;if(this.state.findSessionByKey(t))continue;const n=s.label||s.derivedTitle||s.title||void 0,o=createSessionObject({gatewayKey:t,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,title:n||"New chat"});this.registerSession(o),i++}i>0&&this.logger.info?.(`[bridge-core] preloadSessions: loaded ${i} session(s) from gateway`)}catch(e){this.logger.debug?.("[bridge-core] preloadSessions failed:",e)}else this.logger.debug?.("[bridge-core] preloadSessions skipped: gateway not connected")}async handleRequest(e){this.writeObs({component:"bridge-core",domain:"forward",name:`rpc.request.${e.method}`,severity:"debug",payload:{id:e.id,method:e.method}});try{switch(e.method){case"initialize":this.handleInitialize(e);break;case"session/new":await this.handleSessionNew(e);break;case"session/load":await this.handleSessionLoad(e);break;case"session/list":await this.handleSessionList(e);break;case"session/rename":this.handleSessionRename(e);break;case"session/delete":await this.handleSessionDelete(e);break;case"session/clear":await this.handleSessionClear(e);break;case"session/prompt":await this.handleSessionPrompt(e);break;case"session/set_mode":await this.handleSessionSetMode(e);break;case"session/set_model":await this.handleSessionSetModel(e);break;case"skills/list":await this.handleSkillsList(e);break;case"skills/install":await this.handleSkillsInstall(e);break;case"skills/uninstall":await this.handleSkillsUninstall(e);break;case"skills/update":await this.handleSkillsUpdate(e);break;case"models/list":await this.handleModelsList(e);break;case"session/cancel":this.handleSessionCancel(e.id,e.params);break;case"cron/list":case"cron/status":case"cron/add":case"cron/update":case"cron/remove":case"cron/run":case"cron/runs":await this.handleCronRequest(e);break;default:e.method.startsWith("terminal/")&&this.handleTerminalRequest?await this.handleTerminalRequest(e):this.transport.sendAcpError(e.id,Errors.MethodNotFound,{message:`Method not found: ${e.method}`})}}catch(s){this.transport.sendAcpError(e.id,Errors.InternalError,{message:s instanceof Error?s.message:"Internal error"})}}handleCanvasNotification=null;getCanvasState=null;onSessionLoaded=null;onSessionDeleted=null;onSessionCleared=null;handleNotification(e){switch(e.method){case"session/cancel":this.handleSessionCancel(void 0,e.params);break;case"canvas/toggle":case"canvas/clear":this.handleCanvasNotification?this.handleCanvasNotification(e.method,e.params):this.logger.debug?.(`[bridge-core] Canvas notification ignored (canvas disabled): ${e.method}`);break;case"canvas/reference":{const s=e.params,t=s?.surfaceId,i=this.state.resolveSessionFromParams(s);if(!i){this.logger.warn?.(`[bridge-core] canvas/reference ignored — no session found for sessionId=${s?.sessionId}`);break}i.referencedSurfaceId=t||void 0,this.logger.debug?.(`[bridge-core] Canvas reference ${t?"set":"cleared"}: ${t??"(none)"}`);break}default:e.method.startsWith("terminal/")&&this.handleTerminalNotification?this.handleTerminalNotification(e.method,e.params):this.logger.debug?.(`[bridge-core] Unknown notification: ${e.method}`)}}handleInitialize(e){this.initialized=!0;const s=this.transport.forwardControl;!1===e.params?.forwardThinking&&(s.forwardThinking=!1),!1===e.params?.forwardToolCalls&&(s.forwardToolCalls=!1),this.transport.sendResult(e.id,{protocolVersion:ACP_PROTOCOL_VERSION,agentCapabilities:{loadSession:!0,skills:!0,promptCapabilities:{image:!0,audio:!1,embeddedContext:!0},sessionCapabilities:{list:{},"web-ssh":!!this.config.bridge.shell.enabled,canvas:!!this.config.canvasEnabled,cron:!0},mcp:{}},agentInfo:{name:"beeos-claw",title:"BeeOS Agent Bridge",version:"0.1.0"},_meta:{},authMethods:[]})}async handleSessionNew(e){const s=`agent:${"string"==typeof e.params?.agentId&&e.params.agentId||this.config.gateway.agentId||this.getDefaultAgentId()}:${randomUUID()}`,t="string"==typeof e.params?.cwd?e.params.cwd:".",i=createSessionObject({gatewayKey:s,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,cwd:t});this.registerSession(i),this.transport.sendResult(e.id,{sessionId:i.sessionId,modes:{availableModes:[{...DEFAULT_MODE}],currentModeId:DEFAULT_MODE.id},_meta:{sessionKey:s,agentId:i.agentId,platformAgentId:i.platformAgentId??null}}),this.fetchHistoryInBackground(s)}fetchHistoryInBackground(e){const s=this.getGateway();s?.isConnected&&loadHistory(s,e,50).catch(e=>{this.logger.debug?.(`[bridge-core] Background history fetch failed: ${e instanceof Error?e.message:e}`)})}async recoverSessionFromGateway(e){const s=this.getGateway();if(s?.isConnected)try{const t=await s.sessionsList();if(!t||"object"!=typeof t)return;const i=t.sessions;if(!Array.isArray(i))return;for(const s of i){const t=s,i=t.sessionKey??t.key??t.id??t.name;if(i&&"string"==typeof i&&deriveSessionId(i,this.instanceId)===e){const e=createSessionObject({gatewayKey:i,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels});return this.registerSession(e),e}}}catch(e){this.logger.debug?.("[bridge-core] recoverSessionFromGateway failed:",e)}}async resolveOrCreateSession(e){let s=this.state.sessions.get(e);if(!s&&e.startsWith("agent:")&&(s=this.state.findSessionByKey(e)),s||e.startsWith("agent:")||(s=await this.recoverSessionFromGateway(e)),s)return{session:s,created:!1};const t=e.startsWith("agent:"),i=this.config.gateway.agentId||this.getDefaultAgentId();return s=createSessionObject({gatewayKey:t?e:`agent:${i}:${e}`,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels}),this.registerSession(s),{session:s,created:!0}}async handleSessionLoad(e){const s=e.params?.sessionId;if(!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const{session:t}=await this.resolveOrCreateSession(s),i=t.sessionId;this.state.lastActiveSessionId=i,t.chatReplayDedupKeys.clear();let n=[];try{const e=this.getGateway(),s=await loadHistory(e,t.gatewaySessionKey,50);n=groupIntoTurns(s);const o=n.filter(e=>"user"===e.role).length,r=n.filter(e=>"assistant"===e.role).length,a=n.filter(e=>e.toolCalls?.length).length,d=n.reduce((e,s)=>e+(s.toolCalls?.length??0),0);this.logger.debug?.(`[bridge-core] handleSessionLoad sid=${i} messages=${s.length} turns=${n.length} userTurns=${o} asstTurns=${r} turnsWithToolCalls=${a} totalToolCalls=${d}`);for(let e=0;e<n.length;e++){const s=n[e];if("user"!==s.role||"internal_system"!==s.provenance?.kind)if("user"===s.role&&isHeartbeatPromptText(s.content))this.logger.debug?.(`[bridge-core] SKIP history_turn[${e}] heartbeat cron prompt`);else if("assistant"===s.role&&isHeartbeatOnlyText(s.content))this.logger.debug?.(`[bridge-core] SKIP history_turn[${e}] heartbeat assistant reply`);else if("system"!==s.role){if(this.logger.debug?.(`[bridge-core] SENDING history_turn[${e}] role=${s.role} contentLen=${s.content.length} content=${JSON.stringify(s.content.slice(0,80))} tcCount=${s.toolCalls?.length??0}`),"user"===s.role)this.replayUserTurnAsChunks(i,s.content,s.metadata);else{const e={sessionUpdate:"history_turn",role:s.role,content:s.content,reasoning:s.reasoning,toolCalls:s.toolCalls};s.metadata&&(e.metadata=s.metadata),this.transport.sendSessionUpdate(i,e)}if("assistant"===s.role){if(s.files?.length)for(const e of s.files)this.transport.sendSessionUpdate(i,{sessionUpdate:"agent_message_chunk",content:{type:"resource_link",uri:e.uri,...e.name?{name:e.name}:{},...e.mimeType?{mimeType:e.mimeType}:{}}});s.toolCalls?.length&&await this.replayUploadFileBlocks(i,s.toolCalls)}}else this.logger.debug?.(`[bridge-core] SENDING system_message[${e}] contentLen=${s.content.length}`),this.transport.sendSessionUpdate(i,{sessionUpdate:"system_message_chunk",content:{type:"text",text:s.content},metadata:s.metadata});else this.logger.debug?.(`[bridge-core] SKIP history_turn[${e}] internal_system user message`)}}catch(e){this.logger.warn?.("[bridge-core] Failed to load session history:",e)}const o=new Set;for(const e of n)if(e.toolCalls)for(const s of e.toolCalls)s.id&&o.add(s.id),s.callId&&s.callId!==s.id&&o.add(s.callId);if(void 0!==t.pendingPromptId&&!t.promptDone&&t.assistantMq&&!t.assistantMq.closed)try{this.replayCurrentAssistantStream(t,o)}catch(e){this.logger.warn?.(`[bridge-core] replayCurrentAssistantStream failed: ${e instanceof Error?e.message:e}`)}(()=>{this.transport.sendResult(e.id,{sessionId:i}),this.onSessionLoaded?.(i,t.gatewaySessionKey,t.platformAgentId),this.pushSkillsList().catch(e=>{this.logger.warn?.(`[bridge-core] pushSkillsList failed: ${e instanceof Error?e.message:e}`)})})()}async replayUploadFileBlocks(e,s){for(const t of s){if(t.name!==BEEOS_UPLOAD_TOOL_NAME)continue;let s=[];try{const e=await readUploadToolResult(t.id);e&&(s=buildToolResultSessionContentBlocks(t.name,e))}catch{}if(0===s.length){const e=extractBeeosFileUrisFromOutput(t.output);for(const t of e)s.push({type:"content",content:{type:"resource_link",uri:t,name:"file"}})}const i=s.filter(e=>"resource_link"===e.content?.type);for(const s of i)this.transport.sendSessionUpdate(e,{sessionUpdate:"agent_message_chunk",content:s.content});i.length>0&&this.logger.debug?.(`[bridge-core] replayUploadFileBlocks tcId=${t.id} sent ${i.length} resource_link block(s)`)}}replayCurrentAssistantStream(e,s){if(!e.assistantMq||e.assistantMq.closed)return;const t=e.assistantMq.peek(0);if(0===t.length)return;let i=0,n=0;for(const o of t)s?.size&&o.toolCallId&&s.has(o.toolCallId)&&("tool_call"===o.sessionUpdate||"tool_call_update"===o.sessionUpdate)?n++:(this.transport.sendSessionUpdate(e.sessionId,o),i++);n>0&&this.logger.debug?.(`[bridge-core] replayCurrentAssistantStream skipped ${n} duplicate tool events (already in history)`),i>0&&this.writeObs({component:"bridge-core",domain:"lifecycle",name:"session.load.replay_complete",severity:"debug",payload:{sessionId:e.sessionId,replayed:i}})}async handleSessionList(e){const s=[];for(const[,e]of this.state.sessions)isBackgroundSessionKey(e.gatewaySessionKey)||s.push({sessionId:e.sessionId,title:e.title||"New chat",cwd:e.cwd,updatedAt:e.lastStreamActivityTs??e.createdAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(e.gatewaySessionKey)});const t=this.getGateway();if(t?.isConnected)try{const e=await t.sessionsList();if(e&&"object"==typeof e&&Array.isArray(e.sessions)){const t=e.sessions;for(const e of t){const t=e,i=t.sessionKey??t.key??t.id??t.name;if(!i||"string"!=typeof i)continue;if(isBackgroundSessionKey(i))continue;const n=deriveSessionId(i,this.instanceId),o=s.find(e=>e.sessionId===i||e.sessionId===n||this.state.sessions.get(e.sessionId)?.gatewaySessionKey===i),r=t.label||cleanGatewayDerivedTitle(t.derivedTitle)||t.title||void 0,a=t.model,d=t.modelProvider;if(o){if(!o.title||"New chat"===o.title){const e=r||o.sessionId;o.title=e;const s=this.state.sessions.get(o.sessionId);s&&!s.title&&(s.title=e)}const e=t.updatedAt;e&&(o.updatedAt=e),a&&(o.model=a),d&&(o.modelProvider=d)}else{const e=createSessionObject({gatewayKey:i,instanceId:this.instanceId,resolvePlatformAgentId:e=>this.resolvePlatformAgentId(e),thirdPartyChannels:this.thirdPartyChannels,cwd:t.cwd,title:r||"New chat"});this.registerSession(e),s.push({sessionId:e.sessionId,title:r||"New chat",cwd:t.cwd,updatedAt:t.updatedAt,agentId:e.agentId,platformAgentId:e.platformAgentId,deletable:!isSystemDefaultSessionKey(i),model:a,modelProvider:d})}}}}catch(e){this.logger.debug?.("[bridge-core] sessions.list enrichment failed:",e)}s.sort((e,s)=>(s.updatedAt??0)-(e.updatedAt??0)||e.sessionId.localeCompare(s.sessionId)),this.transport.sendResult(e.id,{sessions:s})}handleSessionRename(e){const s=e.params?.sessionId,t=e.params?.title;if(!s||!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, title"});const i=t.slice(0,64),n=this.state.sessions.get(s);if(!n)return void this.transport.sendAcpError(e.id,Errors.SessionNotFound);n.title=i;const o=n.gatewaySessionKey,r=this.getGateway();r?.isConnected&&r.sessionsPatch(o,{label:i}).catch(e=>{this.logger.debug?.(`[bridge-core] sessionsPatch (rename) failed: ${e instanceof Error?e.message:e}`)}),this.transport.sendResult(e.id,{sessionId:s,title:i})}async handleSessionDelete(e){const s=e.params?.sessionId;if(!s)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const t=this.state.sessions.get(s);if(!t)return this.logger.info?.(`[bridge-core] session/delete sessionId=${s} NOT FOUND in local state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);const i=t.gatewaySessionKey;this.logger.info?.(`[bridge-core] session/delete sessionId=${s} gatewayKey=${i} gwConnected=${!!this.getGateway()?.isConnected}`);let n=!1;const o=this.getGateway();if(o?.isConnected&&i)try{await o.sessionsDelete(i),n=!0,this.logger.info?.(`[bridge-core] Gateway sessions.delete SUCCESS for key=${i}`)}catch(s){const t=s instanceof Error?s.message:String(s);if(this.logger.info?.(`[bridge-core] Gateway sessions.delete FAILED for key=${i}: ${t}`),!t.includes("Cannot delete the main session"))return void this.transport.sendResult(e.id,{deleted:!1});try{await o.sessionsReset(i),n=!0,this.logger.info?.(`[bridge-core] Gateway sessions.reset SUCCESS for key=${i}`)}catch(s){return this.logger.warn?.(`[bridge-core] Gateway sessions.reset also failed for key=${i}: ${s instanceof Error?s.message:s}`),void this.transport.sendResult(e.id,{deleted:!1})}}else i&&this.logger.warn?.(`[bridge-core] Gateway not connected, proceeding with local-only delete for key=${i}`);void 0!==t.pendingPromptId&&this.failPrompt(t,Errors.SessionDeleted.rpcCode,Errors.SessionDeleted.message),this.state.clearPromptTimeout(t),this.state.clearStreamIdleTimer(t),t.assistantMq?.close(),t.currentRunId&&this.state.promptsByRunId.delete(t.currentRunId);const r=new Set;for(const[e,t]of this.state.toolCallToSessionMap)t===s&&(this.state.toolCallToSessionMap.delete(e),r.add(e));for(const[e,s]of this.state.toolCallIdMap)r.has(s)&&this.state.toolCallIdMap.delete(e);this.state.sessions.delete(s),this.state.sessionsByGatewayKey.delete(i),this.transport.resetSessionUpdateCount(s),n&&this.onSessionDeleted?.(i),this.logger.info?.(`[bridge-core] session/delete DONE sessionId=${s} gatewayKey=${i} deleted=true`),this.transport.sendResult(e.id,{deleted:!0})}async handleSessionClear(e){this.logger.info?.(`[bridge-core] handleSessionClear called, params=${JSON.stringify(e.params)}`);const s=e.params?.sessionId;if(!s)return this.logger.warn?.("[bridge-core] session/clear missing sessionId"),void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId"});const t=this.state.sessions.get(s);if(!t)return this.logger.warn?.(`[bridge-core] session/clear sessionId=${s} not found in state`),void this.transport.sendAcpError(e.id,Errors.SessionNotFound);this.logger.info?.(`[bridge-core] session/clear sessionId=${s} gatewayKey=${t.gatewaySessionKey}`);const i=this.getGateway();if(i?.isConnected&&t.gatewaySessionKey)try{await i.sessionsReset(t.gatewaySessionKey),this.logger.info?.(`[bridge-core] Gateway sessions.reset SUCCESS for key=${t.gatewaySessionKey}`)}catch(e){this.logger.warn?.(`[bridge-core] Gateway sessions.reset FAILED for key=${t.gatewaySessionKey}: ${e instanceof Error?e.message:e}`)}else this.logger.warn?.(`[bridge-core] session/clear skipped gateway reset: connected=${i?.isConnected}, key=${t.gatewaySessionKey}`);t.chatReplayDedupKeys.clear(),this.logger.info?.("[bridge-core] session/clear dedupKeys cleared, calling onSessionCleared"),this.onSessionCleared?.(s,t.gatewaySessionKey),this.transport.sendResult(e.id,{cleared:!0}),this.logger.info?.("[bridge-core] session/clear DONE, sent cleared=true")}async handleSessionSetMode(e){const s=e.params?.sessionId,t=e.params?.mode,i=s?this.state.sessions.get(s):void 0,n=this.getGateway();if(i&&n?.isConnected&&t){const e={};if("verbose"===t||"detailed"===t?e.verboseLevel="full":"concise"!==t&&"default"!==t||(e.verboseLevel="default"),"reasoning"!==t&&"thinking"!==t||(e.reasoningLevel="stream"),Object.keys(e).length>0)try{await n.sessionsPatch(i.gatewaySessionKey,e)}catch(e){this.logger.warn?.(`[bridge-core] sessions.patch failed: ${e instanceof Error?e.message:e}`)}}this.transport.sendResult(e.id,{mode:t??"default"})}async handleSessionSetModel(e){const s=e.params?.sessionId,t=e.params?.model,i=s?this.state.sessions.get(s):void 0,n=this.getGateway();let o=t??null,r=null;if(i&&n?.isConnected&&t)try{const e=await n.sessionsPatch(i.gatewaySessionKey,{model:t}),s=e?.resolved;s?.model&&(o=s.model),s?.modelProvider&&(r=s.modelProvider)}catch(e){this.logger.warn?.(`[bridge-core] sessions.patch for model failed: ${e instanceof Error?e.message:e}`)}this.transport.sendResult(e.id,{model:o,modelProvider:r})}async handleModelsList(e){const s=this.getGateway();if(s?.isConnected)try{const t=await s.request("models.list",{});this.transport.sendResult(e.id,t??{models:[]})}catch(s){this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Failed to list models: ${s instanceof Error?s.message:s}`})}else this.transport.sendAcpError(e.id,Errors.GatewayNotConnected)}async handleSkillsList(e){const s=await this.fetchSimplifiedSkills();this.transport.sendResult(e.id,{skills:s})}async fetchSimplifiedSkills(){const e=this.getGateway();if(!e?.isConnected)return[];try{const s=await e.skillsStatus();return s?.skills?s.skills.filter(e=>!1!==e.eligible||!0===e.disabled).sort((e,s)=>e.always&&!s.always?-1:!e.always&&s.always?1:e.name.localeCompare(s.name)).map(e=>({name:e.name,description:e.description,emoji:e.emoji,always:e.always||void 0,bundled:e.bundled||void 0,disabled:e.disabled||void 0,skillKey:e.skillKey||void 0})):[]}catch(e){return this.logger.warn?.(`[bridge-core] Failed to fetch skills: ${e instanceof Error?e.message:e}`),[]}}async pushSkillsList(){const e=await this.fetchSimplifiedSkills();this.transport.sendNotification("session/update",{sessionId:"_system",update:{sessionUpdate:"available_commands_update",commands:e}}),this.writeObs({component:"bridge-core",domain:"lifecycle",name:"skills.pushed",severity:"debug",payload:{count:e.length}})}getSkillsDir(){const e=process.env.OPENCLAW_HOME||join(homedir(),".openclaw");return join(e,"skills")}async handleSkillsInstall(e){const s=e.params,t=s?.name||s?.slug,i=s?.zipUrl,n=s?.files;if(!t||!n?.length&&!i)return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skill name or files/zipUrl"});const o=join(this.getSkillsDir(),t);try{if(await mkdir(o,{recursive:!0}),i){const e=await fetchWithRetry(i,{maxRetries:4,logger:this.logger});await extractZipToDir(e,o)}else if(n?.length)for(const e of n){const s=join(o,e.path);await mkdir(join(s,".."),{recursive:!0});const t=await fetch(e.downloadUrl,{signal:AbortSignal.timeout(12e4)});if(!t.ok)throw new Error(`Failed to download ${e.path}: ${t.status}`);const i=Buffer.from(await t.arrayBuffer());await writeFile(s,i)}this.logger.info?.(`[bridge-core] Installed skill "${t}" via ${i?"zip":"files"}`),this.transport.sendResult(e.id,{success:!0}),await this.pushSkillsList()}catch(s){this.logger.error?.(`[bridge-core] Failed to install skill "${t}": ${s instanceof Error?s.message:s}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Install failed: ${s instanceof Error?s.message:String(s)}`})}}async handleSkillsUninstall(e){const s=e.params,t=s?.name;if(!t)return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skill name"});const i=join(this.getSkillsDir(),t),n=join(homedir(),".agents","skills",t);try{let s=!1;try{const e=await lstat(i);if(e.isSymbolicLink()){const e=await readlink(i);await rm(i,{force:!0}),await rm(e,{recursive:!0,force:!0}),s=!0}else e.isDirectory()&&(await rm(i,{recursive:!0,force:!0}),s=!0)}catch{}try{const e=await lstat(n);(e.isDirectory()||e.isSymbolicLink())&&(await rm(n,{recursive:!0,force:!0}),s=!0)}catch{}s||this.logger.warn?.(`[bridge-core] Skill "${t}" not found in any known location`),this.logger.info?.(`[bridge-core] Uninstalled skill "${t}"`),this.transport.sendResult(e.id,{success:!0}),await this.pushSkillsList()}catch(s){this.logger.error?.(`[bridge-core] Failed to uninstall skill "${t}": ${s instanceof Error?s.message:s}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Uninstall failed: ${s instanceof Error?s.message:String(s)}`})}}async handleSkillsUpdate(e){const s=e.params,t=s?.skillKey,i=s?.enabled;if(!t||"boolean"!=typeof i)return void this.transport.sendAcpError(e.id,Errors.InvalidParams,{message:"Missing skillKey or enabled"});const n=this.getGateway();if(n?.isConnected)try{const s=await n.skillsUpdate(t,i);this.logger.info?.(`[bridge-core] Updated skill "${t}" enabled=${i}`),this.transport.sendResult(e.id,{ok:s?.ok??!0,skillKey:t,enabled:i}),await this.pushSkillsList()}catch(s){this.logger.error?.(`[bridge-core] Failed to update skill "${t}": ${s instanceof Error?s.message:s}`),this.transport.sendAcpError(e.id,Errors.InternalError,{message:`Update failed: ${s instanceof Error?s.message:String(s)}`})}else this.transport.sendAcpError(e.id,Errors.InternalError,{message:"Gateway not connected"})}async handleSessionPrompt(e){const s=e.params?.sessionId,t=e.params?.prompt;if(!s||!t)return void this.transport.sendAcpError(e.id,Errors.MissingParam,{param:"sessionId, prompt"});const{session:i,created:n}=await this.resolveOrCreateSession(s);n&&this.onSessionLoaded?.(i.sessionId,i.gatewaySessionKey,i.platformAgentId);const o=i.sessionId;this.state.lastActiveSessionId=o,this.onPromptStart?.();const r=this.getGateway();if(!r?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);let a=extractPromptBlocks(sanitizePromptPayload(t));if(!i.title){const e=a.find(e=>"text"===e.type&&e.text);e&&"text"in e&&e.text&&(i.title=e.text.slice(0,64).replace(/\n/g," ").trim(),r?.isConnected&&i.title&&r.sessionsPatch(i.gatewaySessionKey,{label:i.title}).catch(e=>{this.logger.debug?.(`[bridge-core] sessionsPatch (title) failed: ${e instanceof Error?e.message:e}`)}))}const d=buildResolutionPlan(a);if(d.refs.length>0)try{const e=await resolveResources(d,this.writeObs);a=applyResolutions(a,e)}catch(e){this.logger.warn?.(`[bridge-core] Resource pre-resolution failed: ${e instanceof Error?e.message:e}`)}let l;if(this.beeosFileResolver){const e=this.beeosFileResolver.buildResolutionPlan(a);if(e.refs.length>0)try{l=await this.beeosFileResolver.resolveMetadataForPrompt(e)}catch(e){this.logger.warn?.(`[bridge-core] BeeOS file resolution failed: ${e instanceof Error?e.message:e}`)}}const c=toGatewayPrompt(a,{beeosFileResolutionsByUri:l});if("error"in c)return void this.transport.sendAcpError(e.id,Errors.PromptConvertFailed,{message:c.error});const{message:p,attachments:h}=c,m=resolveStandaloneSlashCommandName(a,p)??resolveSlashCmd(a,p),g="string"==typeof m;if(m&&this.handleSlashCommand){const s=this;if(await(async()=>{try{return await s.handleSlashCommand(m)}catch(e){return s.logger.warn?.(`[bridge-core] /${m} handler failed: ${e instanceof Error?e.message:e}`),!1}})())return this.emitPromptAsUserSessionUpdates(i.sessionId,a),this.transport.sendSessionUpdate(o,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:`Loading ${m}...`}}),void this.transport.sendResult(e.id,{stopReason:"end_turn"})}const u="chat.send";let f=g?p:withUserMessagePrefix(p);const y=this.config.canvasEnabled&&!g&&!1!==i.canvasActive&&!i.channelType;if(y){const e=i.promptsSinceCanvasInjection??0;!i.canvasPromptInjected||e>=10?(f=`${CANVAS_TERM_MAPPING}\n\n${f}`,i.canvasPromptInjected=!0,i.promptsSinceCanvasInjection=0,this.logger.debug?.("[bridge-core] Canvas term mapping injected (re-inject cycle)")):i.promptsSinceCanvasInjection=e+1}if(y){const e=this.getCanvasState?.();if(e){const s=e.resolveCanvasForSession(i.gatewaySessionKey);f=`${buildActiveSurfacesContext(e,s)??"[Canvas] Canvas panel is available. Use render_ui for structured/visual content."}\n\n${f}`}const s=i.referencedSurfaceId;s&&(f=`${CANVAS_UPDATE_HINT}\n[IMPORTANT: The user selected canvas surface "${s}" for editing. You MUST call render_ui with id="${s}" to UPDATE this existing surface. Do NOT create a new surface with a different id.]\n${f}`,i.referencedSurfaceId=void 0)}if(!g){const s=parseMentionsParam(e.params?.mentions);if(s.length>0){i.activeMentions=s;f=`${buildMentionInstruction(s)}\n${f}`,this.logger.debug?.(`[bridge-core] A2A mention instruction injected for ${s.length} agent(s)`)}else i.activeMentions=void 0}g&&this.logger.info?.(`[bridge-core] slash command passthrough sessionId=${o} command=/${m}`),i.promptDone||void 0===i.pendingPromptId&&void 0===i.currentRunId||(this.logger.warn?.(`[bridge-core] handleSessionPrompt: force-ending stale run sid=${o} oldRunId=${i.currentRunId} oldPromptId=${i.pendingPromptId}`),(i.currentRunId||i.gatewayRequestId)&&r.agentCancel(i.gatewaySessionKey,{runId:i.currentRunId,requestId:i.gatewayRequestId}),this.endRun(i,{kind:"complete",stopReason:"cancelled"})),this.state.resetForNewRun(i,o,this.transport),i.pendingPromptId=e.id,i.gatewayMethod=u,this.emitPromptAsUserSessionUpdates(o,a),this.transport.sendSessionUpdate(o,{sessionUpdate:"agent_message_chunk",content:{type:"text",text:""}}),this.schedulePromptTimeout(i);const w=h.length>0?h.map(e=>({type:e.type,content:e.content,...e.mimeType?{mimeType:e.mimeType}:{},...e.fileName?{fileName:e.fileName}:{}})):void 0,S=`req_${randomUUID().replace(/-/g,"")}`,I={sessionKey:i.gatewaySessionKey,message:f,deliver:!1,idempotencyKey:randomUUID()};w?.length&&(I.attachments=w);if(!r.sendFrame({type:"req",id:S,method:u,params:I}))return this.state.clearPromptTimeout(i),i.pendingPromptId=void 0,i.assistantMq?.close(),void this.transport.sendAcpError(e.id,Errors.SendFailed,{message:"failed to send prompt to gateway"});i.gatewayRequestId=S,this.state.promptsByGatewayRequestId.set(S,i)}replayUserTurnAsChunks(e,s,t){const i=splitUserReplayBlocks(s);for(const s of i){let i;if("text"===s.type){if(!s.text)continue;i={type:"text",text:s.text}}else i="resource_link"===s.type?{type:"resource_link",uri:s.uri??"",...s.fileName?{name:s.fileName}:{},...s.mimeType?{mimeType:s.mimeType}:{}}:{type:s.type,...s.mimeType?{mimeType:s.mimeType}:{},...s.fileName?{fileName:s.fileName}:{}};this.transport.sendSessionUpdate(e,{sessionUpdate:"user_message_chunk",content:i,...t?{metadata:t}:{}})}}emitPromptAsUserSessionUpdates(e,s){for(const t of s){const s=this.toUserSessionUpdateContent(t);s&&this.transport.sendSessionUpdate(e,{sessionUpdate:"user_message_chunk",content:s})}}toUserSessionUpdateContent(e){if("text"===e.type){if(!e.text)return;return{type:"text",text:e.text}}if("image"===e.type){const s=e.mimeType??e.mime_type??"application/octet-stream",t=e.fileName??e.file_name??e.name;if(!e.data&&!e.uri)return;return{type:"image",...e.data?{data:e.data}:{},...e.uri?{uri:e.uri}:{},...s?{mimeType:s}:{},...t?{fileName:t}:{}}}if("resource_link"===e.type){if(!e.uri)return;return{type:"resource_link",uri:e.uri,...e.title?{title:e.title}:{},...e.name?{name:e.name}:{},...e.mimeType??e.mime_type?{mimeType:e.mimeType??e.mime_type}:{}}}if("file"===e.type){const s=e.mimeType??e.mime_type??"application/octet-stream",t=e.fileName??e.file_name??e.filename??e.name;if(!e.data&&!e.text&&!e.uri)return;return{type:"file",...e.data?{data:e.data}:{},...e.text?{text:e.text}:{},...e.uri?{uri:e.uri}:{},...s?{mimeType:s}:{},...t?{fileName:t}:{}}}if("resource"===e.type){const s=e.resource??{},t=s.uri??e.uri,i=s.mimeType??s.mime_type??e.mimeType??e.mime_type,n=s.text??e.text,o=s.data??e.data,r=s.fileName??s.file_name??s.filename??s.name??e.fileName??e.file_name??e.filename??e.name;if(!t&&!n&&!o)return;return{type:"resource",resource:{...t?{uri:t}:{},...i?{mimeType:i}:{},...n?{text:n}:{},...o?{data:o}:{},...r?{fileName:r}:{}}}}}handleSessionCancel(e,s){const t=s?.sessionId;if(!t)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.MissingParam,{param:"sessionId"}));const i=this.state.sessions.get(t);if(!i)return void(void 0!==e&&this.transport.sendAcpError(e,Errors.SessionNotFound));const n=this.getGateway(),o=n?.isConnected,r=!(!i.currentRunId&&!i.gatewayRequestId);if(o&&r){n.agentCancel(i.gatewaySessionKey,{runId:i.currentRunId,requestId:i.gatewayRequestId})}this.endRun(i,{kind:"complete",stopReason:"cancelled"})||this.transport.sendSessionUpdate(i.sessionId,{sessionUpdate:"stream_complete",stopReason:"cancelled"}),void 0!==e&&this.transport.sendResult(e,{})}endRun(e,s){if(e.promptDone)return this.logger.debug?.(`[bridge-core] endRun skipped (promptDone=true) sessionId=${e.sessionId} outcome=${s.kind}`),!1;if(void 0===e.pendingPromptId&&void 0===e.currentRunId)return this.logger.debug?.(`[bridge-core] endRun skipped (no pendingPromptId and no currentRunId) sessionId=${e.sessionId} outcome=${s.kind}`),!1;e.promptDone=!0;const t=e.currentRunId;if("complete"===s.kind){if(t&&this.onRunComplete){const s=e.agentAssistantTextSoFar??e.chatAssistantTextSoFar??"";this.onRunComplete(t,s)}void 0!==e.pendingPromptId&&this.transport.sendResult(e.pendingPromptId,{stopReason:s.stopReason}),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:s.stopReason})}else t&&this.onRunFailed&&this.onRunFailed(t,s.message),void 0!==e.pendingPromptId&&this.transport.sendError(e.pendingPromptId,s.code,s.message),this.transport.sendSessionUpdate(e.sessionId,{sessionUpdate:"stream_complete",stopReason:"error",error:s.message});return this.cleanupPrompt(e),this.onRunEnded?.(e,s),!0}schedulePromptTimeout(e){this.state.clearPromptTimeout(e);const s=this.config.promptTimeoutMs??DEFAULT_PROMPT_TIMEOUT_MS;s<=0||(e.promptTimeoutTimer=setTimeout(()=>{void 0!==e.pendingPromptId&&(this.logger.warn?.(`[bridge-core] Prompt timed out sessionId=${e.sessionId} after ${s}ms`),this.failPrompt(e,Errors.PromptTimeout.rpcCode,`Prompt timed out after ${s}ms`))},s),"object"==typeof e.promptTimeoutTimer&&"unref"in e.promptTimeoutTimer&&e.promptTimeoutTimer.unref())}cleanupPrompt(e){this.state.clearPromptTimeout(e),this.state.clearStreamIdleTimer(e);const s=e.currentRunId;if(s){this.state.promptsByRunId.delete(s),this.state.markRunCompleted(s);const e=`tc_${s}_`;for(const[s,t]of this.state.toolCallIdMap)t.startsWith(e)&&this.state.toolCallIdMap.delete(s)}e.gatewayRequestId&&this.state.promptsByGatewayRequestId.delete(e.gatewayRequestId),e.pendingPromptId=void 0,e.promptDone=void 0,e.currentRunId=void 0,e.gatewayRequestId=void 0,e.assistantStreamStartedAt=void 0,e.gatewayMethod=void 0,e.assistantMq?.close()}completePrompt(e,s){this.endRun(e,{kind:"complete",stopReason:s})}failPrompt(e,s,t){this.endRun(e,{kind:"error",code:s,message:t})}cleanupAllPendingPrompts(){for(const e of this.state.sessions.values())this.failPrompt(e,Errors.GatewayDisconnected.rpcCode,Errors.GatewayDisconnected.message)}handleGatewayResponse(e){const s=e.id;if(!s)return;const t=this.state.promptsByGatewayRequestId.get(s);if(!t||t.promptDone)return;if(!e.ok){const i=e.error,n=i?.message??"gateway returned error";return this.logger.warn?.(`[bridge-core] handleGatewayResponse: gateway rejected prompt gatewayRequestId=${s} error=${n}`),void this.failPrompt(t,Errors.SendFailed.rpcCode,n)}const i=e.payload,n=i?.runId;n?(t.currentRunId=n,t.assistantStreamStartedAt=Date.now(),this.state.promptsByRunId.set(n,t),this.logger.debug?.(`[bridge-core] handleGatewayResponse: bound runId=${n} gatewayRequestId=${s} sessionId=${t.sessionId}`)):this.logger.debug?.(`[bridge-core] handleGatewayResponse: no runId in payload gatewayRequestId=${s} sessionId=${t.sessionId}`)}maybeCompletePromptFromChatState(e,s){if(e.promptDone||"chat.send"!==e.gatewayMethod)return;const t="string"==typeof s.state?s.state.toLowerCase():void 0;if(t)if("final"===t)this.completePrompt(e,"end_turn");else if("aborted"===t||"cancelled"===t||"cancel"===t)this.completePrompt(e,"cancelled");else if("error"===t){const t=("string"==typeof s.summary?s.summary:void 0)??("string"==typeof s.message?s.message:void 0)??"gateway chat error";this.failPrompt(e,Errors.AgentError.rpcCode,formatFriendlyError(t))}}pushToolResultContent(e,s,t,i){if(0===t.length)return;const n=i?this.state.promptsByRunId.get(i):void 0;if(!n)return void this.logger.warn?.(`[bridge-core] pushToolResultContent dropped — no session for runId=${i??"(none)"}`);const o=n.sessionId;for(const e of t){const s={sessionUpdate:"agent_message_chunk",content:e};this.transport.sendSessionUpdate(o,s),n.assistantMq?.push(s)}this.writeObs({component:"bridge-core",domain:"tool",name:"tool_result.direct_push",severity:"info",payload:{toolCallId:e,toolName:s,blockCount:t.length,sessionId:o}})}async handleCronRequest(e){const s=this.getGateway();if(!s?.isConnected)return void this.transport.sendAcpError(e.id,Errors.GatewayNotConnected);const t=e.method.replace("/",".");try{const i=await s.request(t,e.params??{});this.transport.sendResult(e.id,i??{})}catch(s){const i=s instanceof Error?s.message:"cron request failed";this.logger.warn?.(`[bridge-core] ${t} failed: ${i}`),this.transport.sendAcpError(e.id,Errors.SendFailed,{message:i})}}}async function fetchWithRetry(e,s={}){const t=s.maxRetries??3;let i;for(let n=0;n<=t;n++){const o=await fetch(e,{signal:AbortSignal.timeout(12e4),redirect:"follow"});if(o.ok)return Buffer.from(await o.arrayBuffer());if(429===o.status||o.status>=500){const r=Number(o.headers.get("retry-after"))||0,a=1e3*Math.max(r,2**n);if(i=new Error(`HTTP ${o.status} from ${e}`),n<t){s.logger?.info?.(`[bridge-core] Download ${o.status}, retry ${n+1}/${t} in ${(a/1e3).toFixed(0)}s`),await new Promise(e=>setTimeout(e,a));continue}}throw new Error(`Failed to download zip: ${o.status}`)}throw i??new Error("Download failed after retries")}const SKILL_ROOT_MARKERS=["SKILL.md","skill.md","SKILL.MD"];async function resolveSkillRoot(e){for(const s of SKILL_ROOT_MARKERS)try{return await stat(join(e,s)),e}catch{}const s=(await readdir(e,{withFileTypes:!0})).filter(e=>e.isDirectory()&&!e.name.startsWith(".")&&"__MACOSX"!==e.name);if(1===s.length){const t=join(e,s[0].name);for(const e of SKILL_ROOT_MARKERS)try{return await stat(join(t,e)),t}catch{}return t}return e}async function extractZipToDir(e,s){const t=new AdmZip(e),i=join(s,".beeos-extract-tmp-"+Date.now());try{await mkdir(i,{recursive:!0}),t.extractAllTo(i,!0);const e=await resolveSkillRoot(i),n=await readdir(e,{withFileTypes:!0});let o=0;for(const t of n){if(t.name.startsWith(".beeos-extract-tmp")||"__MACOSX"===t.name)continue;const i=join(e,t.name),n=join(s,t.name);await rename(i,n).catch(async()=>{await cp(i,n,{recursive:!0})}),o++}if(0===o)throw new Error("Zip archive contained no extractable files")}finally{await rm(i,{recursive:!0,force:!0}).catch(()=>{})}}
|
package/dist/acp-server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{WebSocketServer,WebSocket}from"ws";import{randomUUID}from"node:crypto";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{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;messageSdk=null;msgPrincipalId=null;pendingToolCallSessionKeys=[];thirdPartyChannels;agentSyncRetryTimer=null;bridgeInstance=null;bridgeConsecutiveFailures=0;static RE_DISCOVER_THRESHOLD=5;drainNextDeferredSend(e){const t=e.pendingAgentSends?.shift();t&&(e.pendingAgentSends?.length||(e.pendingAgentSends=void 0),this.agentSend(t.message,t.attachments,e.gatewaySessionKey,t.inputProvenance).catch(e=>this.logger.warn?.("[acp-server] Deferred agentSend failed: %s",e)))}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,i){if(!this.gateway?.isConnected)throw new Error("Gateway not connected");const a=this.config.gateway.agentId||this.core.getDefaultAgentId();let r,o;if(s?(r=this.state.findSessionByKey(s),o=s,r?this.logger.info?.(`[acp-server] agentSend: resolved session for key=${s} sid=${r.sessionId}`):(r=this.core.ensureSessionForKey(o),this.logger.info?.(`[acp-server] agentSend: created session for key=${s} sid=${r.sessionId}`))):(o=`agent:${a}:a2a:${randomUUID()}`,r=this.core.ensureSessionForKey(o),this.logger.info?.(`[acp-server] agentSend: no targetSessionKey, created A2A session key=${o}`)),i&&r&&(r.channelId=i),r&&!r.promptDone&&(void 0!==r.pendingPromptId||void 0!==r.currentRunId||void 0!==r.gatewayRequestId))return(r.pendingAgentSends??=[]).push({message:e,attachments:t,inputProvenance:n}),void this.logger.info?.("[acp-server] Session has active run, deferred message (queue=%d)",r.pendingAgentSends.length);const c=await this.canvasOrch.enrichMessageWithCanvasContext(e,o,this.thirdPartyChannels),l=await this.gateway.agentSend(o,c,a,t,n);return l?.runId&&(r?(this.state.resetForNewRun(r,r.sessionId,this.transport),r.currentRunId=l.runId,r.assistantStreamStartedAt=Date.now(),this.state.promptsByRunId.set(l.runId,r)):this.state.promptsByRunId.set(l.runId,{gatewaySessionKey:o,channelId:i})),l}setMessageSdk(e,t){this.messageSdk=e,this.msgPrincipalId=t}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,void 0,()=>{const e=this.client;return!!e&&e.readyState===WebSocket.OPEN}),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.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),failPrompt:(e,t,s)=>this.core.failPrompt(e,t,s),maybeCompletePromptFromChatState:(e,t)=>this.core.maybeCompletePromptFromChatState(e,t),agentFetch:this.agentFetchFn,onStreamDelta:(e,t)=>{if(!this.messageSdk||!this.msgPrincipalId)return;const s=e.gatewaySessionKey;if(!s)return;const n=e.channelId,i={session_key:s,update:t};(n&&this.messageSdk.sendToChannel?this.messageSdk.sendToChannel(n,"agent_reply_delta",i):this.messageSdk.sendToPersonal(this.msgPrincipalId,"agent_reply_delta",i)).catch(e=>this.logger.debug?.(`[acp-server] agent_reply_delta publish failed: ${e}`))}}),this.core.onRunEnded=(e,t)=>{"complete"===t.kind?this.canvasOrch.onPromptComplete(e):this.canvasOrch.onPromptFailed(),this.drainNextDeferredSend(e)},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)=>{const s=this.state.promptsByRunId.get(e),n=s?.gatewaySessionKey,i=s?.channelId;if(this.logger.info?.(`[acp-server] onRunComplete runId=${e} session_key=${n} channel=${i} principal=${this.msgPrincipalId} hasSDK=${!!this.messageSdk}`),this.messageSdk&&this.msgPrincipalId&&n){const e={session_key:n,text:t};(i&&this.messageSdk.sendToChannel?this.messageSdk.sendToChannel(i,"agent_reply",e):this.messageSdk.sendToPersonal(this.msgPrincipalId,"agent_reply",e)).then(()=>{this.logger.info?.(`[acp-server] agent_reply published ok session_key=${n} channel=${i??"personal"}`)}).catch(e=>this.logger.warn?.(`[acp-server] agent_reply publish failed: ${e instanceof Error?e.message:JSON.stringify(e)}`))}else n||this.logger.warn?.(`[acp-server] onRunComplete: no session_key for runId=${e}, cannot publish reply`)},this.core.onRunFailed=(e,t)=>{const s=this.state.promptsByRunId.get(e),n=s?.gatewaySessionKey,i=s?.channelId;if(this.logger.info?.(`[acp-server] onRunFailed runId=${e} session_key=${n} channel=${i}`),this.messageSdk&&this.msgPrincipalId&&n){const e={session_key:n,error:t||"execution failed"};(i&&this.messageSdk.sendToChannel?this.messageSdk.sendToChannel(i,"agent_reply",e):this.messageSdk.sendToPersonal(this.msgPrincipalId,"agent_reply",e)).catch(e=>this.logger.warn?.(`[acp-server] agent_reply error publish failed: ${e instanceof Error?e.message:String(e)}`))}},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){const t=Date.now();this.logger.info?.("[acp-server] start() begin"),e&&(this.config.bridge.url=e);const s=this.config.bridge.url,n=this.config.keyFile;if(s&&!BridgeClientCtor)try{const e=Date.now(),t=await import("@beeos-ai/bridge-client");BridgeClientCtor=t.BridgeClient,this.logger.info?.(`[acp-server] bridge-client import took ${Date.now()-e}ms`)}catch(e){this.logger.warn?.("[acp-server] Bridge SDK not available")}const i=Date.now();if(await this.connectGateway(),this.logger.info?.(`[acp-server] connectGateway() took ${Date.now()-i}ms`),s&&n&&BridgeClientCtor){Date.now();await this.startBridgeMode(s,n)}else this.logger.warn?.("[acp-server] Bridge not configured; running in standby (no ACP listener). Use handleUpgrade() for noServer mode."),this.core.markAgentSyncDone();this.logger.info?.(`[acp-server] start() total ${Date.now()-t}ms`)}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;const i=Date.now();try{await s.connect(),this.logger.info?.(`[acp-server] bridge.connect() took ${Date.now()-i}ms`)}catch(e){this.logger.warn?.(`[acp-server] Initial bridge connect failed after ${Date.now()-i}ms (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)},s=Date.now();try{const e=await syncAgentsToPlatform({gateway:this.gateway,agentGatewayUrl:this.config.agentGatewayUrl,capabilities:{canvas:this.config.canvasEnabled,terminal:!0,fileSystem:!0,cron:!0,streaming:!0},agentFetch:this.agentFetchFn,logger:this.logger});e?(this.core.setAgentMapping(e),this.logger.info?.(`[acp-server] agent mapping loaded: ${[...e.keys()].join(", ")} (sync took ${Date.now()-s}ms)`)):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)),this.gateway.on("response",e=>this.core.handleGatewayResponse(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.logger.info?.(`[acp-server] transport.${e.event} durationMs=${e.durationMs??"?"}`),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)??this.core.ensureSessionForKey(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)??this.core.ensureSessionForKey(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()}getLocalAgentId(){return this.config.gateway.agentId||this.core.getDefaultAgentId()}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.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";import{randomUUID}from"node:crypto";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{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;messageSdk=null;msgPrincipalId=null;pendingToolCallSessionKeys=[];thirdPartyChannels;agentSyncRetryTimer=null;bridgeInstance=null;bridgeConsecutiveFailures=0;static RE_DISCOVER_THRESHOLD=2;drainNextDeferredSend(e){const t=e.pendingAgentSends?.shift();t&&(e.pendingAgentSends?.length||(e.pendingAgentSends=void 0),this.agentSend(t.message,t.attachments,e.gatewaySessionKey,t.inputProvenance).catch(e=>this.logger.warn?.("[acp-server] Deferred agentSend failed: %s",e)))}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,i){if(!this.gateway?.isConnected)throw new Error("Gateway not connected");const a=this.config.gateway.agentId||this.core.getDefaultAgentId();let r,o;if(s?(r=this.state.findSessionByKey(s),o=s,r?this.logger.info?.(`[acp-server] agentSend: resolved session for key=${s} sid=${r.sessionId}`):(r=this.core.ensureSessionForKey(o),this.logger.info?.(`[acp-server] agentSend: created session for key=${s} sid=${r.sessionId}`))):(o=`agent:${a}:a2a:${randomUUID()}`,r=this.core.ensureSessionForKey(o),this.logger.info?.(`[acp-server] agentSend: no targetSessionKey, created A2A session key=${o}`)),i&&r&&(r.channelId=i),r&&!r.promptDone&&(void 0!==r.pendingPromptId||void 0!==r.currentRunId||void 0!==r.gatewayRequestId))return(r.pendingAgentSends??=[]).push({message:e,attachments:t,inputProvenance:n}),void this.logger.info?.("[acp-server] Session has active run, deferred message (queue=%d)",r.pendingAgentSends.length);const c=await this.canvasOrch.enrichMessageWithCanvasContext(e,o,this.thirdPartyChannels),l=await this.gateway.agentSend(o,c,a,t,n);return l?.runId&&(r?(this.state.resetForNewRun(r,r.sessionId,this.transport),r.currentRunId=l.runId,r.assistantStreamStartedAt=Date.now(),this.state.promptsByRunId.set(l.runId,r)):this.state.promptsByRunId.set(l.runId,{gatewaySessionKey:o,channelId:i})),l}setMessageSdk(e,t){this.messageSdk=e,this.msgPrincipalId=t}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,void 0,()=>{const e=this.client;return!!e&&e.readyState===WebSocket.OPEN}),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.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),failPrompt:(e,t,s)=>this.core.failPrompt(e,t,s),maybeCompletePromptFromChatState:(e,t)=>this.core.maybeCompletePromptFromChatState(e,t),agentFetch:this.agentFetchFn,onStreamDelta:(e,t)=>{if(!this.messageSdk||!this.msgPrincipalId)return;const s=e.gatewaySessionKey;if(!s)return;const n=e.channelId,i={session_key:s,update:t};(n&&this.messageSdk.sendToChannel?this.messageSdk.sendToChannel(n,"agent_reply_delta",i):this.messageSdk.sendToPersonal(this.msgPrincipalId,"agent_reply_delta",i)).catch(e=>this.logger.debug?.(`[acp-server] agent_reply_delta publish failed: ${e}`))}}),this.core.onRunEnded=(e,t)=>{"complete"===t.kind?this.canvasOrch.onPromptComplete(e):this.canvasOrch.onPromptFailed(),this.drainNextDeferredSend(e)},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)=>{const s=this.state.promptsByRunId.get(e),n=s?.gatewaySessionKey,i=s?.channelId;if(this.logger.info?.(`[acp-server] onRunComplete runId=${e} session_key=${n} channel=${i} principal=${this.msgPrincipalId} hasSDK=${!!this.messageSdk}`),this.messageSdk&&this.msgPrincipalId&&n){const e={session_key:n,text:t};(i&&this.messageSdk.sendToChannel?this.messageSdk.sendToChannel(i,"agent_reply",e):this.messageSdk.sendToPersonal(this.msgPrincipalId,"agent_reply",e)).then(()=>{this.logger.info?.(`[acp-server] agent_reply published ok session_key=${n} channel=${i??"personal"}`)}).catch(e=>this.logger.warn?.(`[acp-server] agent_reply publish failed: ${e instanceof Error?e.message:JSON.stringify(e)}`))}else n||this.logger.warn?.(`[acp-server] onRunComplete: no session_key for runId=${e}, cannot publish reply`)},this.core.onRunFailed=(e,t)=>{const s=this.state.promptsByRunId.get(e),n=s?.gatewaySessionKey,i=s?.channelId;if(this.logger.info?.(`[acp-server] onRunFailed runId=${e} session_key=${n} channel=${i}`),this.messageSdk&&this.msgPrincipalId&&n){const e={session_key:n,error:t||"execution failed"};(i&&this.messageSdk.sendToChannel?this.messageSdk.sendToChannel(i,"agent_reply",e):this.messageSdk.sendToPersonal(this.msgPrincipalId,"agent_reply",e)).catch(e=>this.logger.warn?.(`[acp-server] agent_reply error publish failed: ${e instanceof Error?e.message:String(e)}`))}},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){const t=Date.now();this.logger.info?.("[acp-server] start() begin"),e&&(this.config.bridge.url=e);const s=this.config.bridge.url,n=this.config.keyFile;if(s&&!BridgeClientCtor)try{const e=Date.now(),t=await import("@beeos-ai/bridge-client");BridgeClientCtor=t.BridgeClient,this.logger.info?.(`[acp-server] bridge-client import took ${Date.now()-e}ms`)}catch(e){this.logger.warn?.("[acp-server] Bridge SDK not available")}const i=Date.now();if(await this.connectGateway(),this.logger.info?.(`[acp-server] connectGateway() took ${Date.now()-i}ms`),s&&n&&BridgeClientCtor){Date.now();await this.startBridgeMode(s,n)}else this.logger.warn?.("[acp-server] Bridge not configured; running in standby (no ACP listener). Use handleUpgrade() for noServer mode."),this.core.markAgentSyncDone();this.logger.info?.(`[acp-server] start() total ${Date.now()-t}ms`)}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;const i=Date.now();try{await s.connect(),this.logger.info?.(`[acp-server] bridge.connect() took ${Date.now()-i}ms`)}catch(e){this.logger.warn?.(`[acp-server] Initial bridge connect failed after ${Date.now()-i}ms (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)},s=Date.now();try{const e=await syncAgentsToPlatform({gateway:this.gateway,agentGatewayUrl:this.config.agentGatewayUrl,capabilities:{canvas:this.config.canvasEnabled,terminal:!0,fileSystem:!0,cron:!0,streaming:!0},agentFetch:this.agentFetchFn,logger:this.logger});e?(this.core.setAgentMapping(e),this.logger.info?.(`[acp-server] agent mapping loaded: ${[...e.keys()].join(", ")} (sync took ${Date.now()-s}ms)`)):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)),this.gateway.on("response",e=>this.core.handleGatewayResponse(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.logger.info?.(`[acp-server] transport.${e.event} durationMs=${e.durationMs??"?"}`),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)??this.core.ensureSessionForKey(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)??this.core.ensureSessionForKey(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()}getLocalAgentId(){return this.config.gateway.agentId||this.core.getDefaultAgentId()}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.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/message-filter.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const TIMESTAMP_PREFIX_RE=/^\s*\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}(?::\d{2})?\s+GMT[+-]\d{1,2}(?::?\d{2})?]\s*/i,WORKDIR_PREFIX_RE=/^\s*\[working directory:[^\]]*]\s*/i,MESSAGE_ID_PREFIX_RE=/^\s*\[message_id:[^\]]*]\s*/i,USER_MSG_PREFIX_RE=/^\s*User Message From \w+:\s*/i,HEARTBEAT_OK_LINE="HEARTBEAT_OK",HEARTBEAT_OK_LINE_RE=/^[\s\p{P}\p{S}]*HEARTBEAT_OK[\s\p{P}\p{S}]*$/u,METADATA_PREFIX_PATTERNS=[TIMESTAMP_PREFIX_RE,WORKDIR_PREFIX_RE,MESSAGE_ID_PREFIX_RE,USER_MSG_PREFIX_RE],UNTRUSTED_METADATA_BLOCK_RE=/^Conversation info \(untrusted metadata\):[\s\S]*?Current time:\s*.*?$/i;function stripMetadataPrefixesFromLine(t){let e=t,r=!1,n=!0;for(;n;){n=!1;for(const t of METADATA_PREFIX_PATTERNS)t.test(e)&&(e=e.replace(t,""),r=!0,n=!0)}return{line:e,changed:r}}export function stripTransportMetadata(t){if(!(t.includes("[")||t.includes("HEARTBEAT_OK")||t.includes("User Message From")||UNTRUSTED_METADATA_BLOCK_RE.test(t.trim())))return t;if(UNTRUSTED_METADATA_BLOCK_RE.test(t.trim()))return"";const e=t.split(/\r?\n/),r=[];let n=!1;for(const t of e){const e=stripMetadataPrefixesFromLine(t);HEARTBEAT_OK_LINE_RE.test(e.line.trim())?n=!0:e.changed&&(n=!0,!e.line.trim())||r.push(e.line)}return n?r.join("\n").replace(/^\n+/,"").replace(/\n+$/,""):t}export function normalizeTransportText(t){if(!t)return;const e=stripTransportMetadata(t);return e.length>0?e:void 0}export function isHeartbeatOnlyText(t){const e=stripTransportMetadata(t).trim();return!!e&&HEARTBEAT_OK_LINE_RE.test(e)}function isRecord(t){return!!t&&"object"==typeof t&&!Array.isArray(t)}function sanitizeNode(t){if("string"==typeof t){const e=stripTransportMetadata(t);return{value:e,changed:e!==t}}if(Array.isArray(t)){let e=!1;const r=t.map(t=>{const r=sanitizeNode(t);return r.changed&&(e=!0),r.value});return{value:e?r:t,changed:e}}if(!isRecord(t))return{value:t,changed:!1};let e=!1,r=t;if("string"==typeof t.type&&"text"===t.type&&"string"==typeof t.text){const n=stripTransportMetadata(t.text);n!==t.text&&(r={...r,text:n},e=!0)}if(Array.isArray(t.content)){const n=sanitizeNode(t.content);n.changed&&(e||(r={...r}),r.content=n.value,e=!0)}return{value:e?r:t,changed:e}}export function sanitizePromptPayload(t){return sanitizeNode(t).value}
|
|
1
|
+
const TIMESTAMP_PREFIX_RE=/^\s*\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}(?::\d{2})?\s+GMT[+-]\d{1,2}(?::?\d{2})?]\s*/i,WORKDIR_PREFIX_RE=/^\s*\[working directory:[^\]]*]\s*/i,MESSAGE_ID_PREFIX_RE=/^\s*\[message_id:[^\]]*]\s*/i,USER_MSG_PREFIX_RE=/^\s*User Message From \w+:\s*/i,HEARTBEAT_OK_LINE="HEARTBEAT_OK",HEARTBEAT_OK_LINE_RE=/^[\s\p{P}\p{S}]*HEARTBEAT_OK[\s\p{P}\p{S}]*$/u,METADATA_PREFIX_PATTERNS=[TIMESTAMP_PREFIX_RE,WORKDIR_PREFIX_RE,MESSAGE_ID_PREFIX_RE,USER_MSG_PREFIX_RE],UNTRUSTED_METADATA_BLOCK_RE=/^Conversation info \(untrusted metadata\):[\s\S]*?Current time:\s*.*?$/i;function stripMetadataPrefixesFromLine(t){let e=t,r=!1,n=!0;for(;n;){n=!1;for(const t of METADATA_PREFIX_PATTERNS)t.test(e)&&(e=e.replace(t,""),r=!0,n=!0)}return{line:e,changed:r}}export function stripTransportMetadata(t){if(!(t.includes("[")||t.includes("HEARTBEAT_OK")||t.includes("User Message From")||UNTRUSTED_METADATA_BLOCK_RE.test(t.trim())))return t;if(UNTRUSTED_METADATA_BLOCK_RE.test(t.trim()))return"";const e=t.split(/\r?\n/),r=[];let n=!1;for(const t of e){const e=stripMetadataPrefixesFromLine(t);HEARTBEAT_OK_LINE_RE.test(e.line.trim())?n=!0:e.changed&&(n=!0,!e.line.trim())||r.push(e.line)}return n?r.join("\n").replace(/^\n+/,"").replace(/\n+$/,""):t}export function normalizeTransportText(t){if(!t)return;const e=stripTransportMetadata(t);return e.length>0?e:void 0}export function isHeartbeatOnlyText(t){const e=stripTransportMetadata(t).trim();return!!e&&HEARTBEAT_OK_LINE_RE.test(e)}const HEARTBEAT_PROMPT_PREFIX="read heartbeat.md if it exists";export function isHeartbeatPromptText(t){return t.slice(0,80).toLowerCase().startsWith(HEARTBEAT_PROMPT_PREFIX)}function isRecord(t){return!!t&&"object"==typeof t&&!Array.isArray(t)}function sanitizeNode(t){if("string"==typeof t){const e=stripTransportMetadata(t);return{value:e,changed:e!==t}}if(Array.isArray(t)){let e=!1;const r=t.map(t=>{const r=sanitizeNode(t);return r.changed&&(e=!0),r.value});return{value:e?r:t,changed:e}}if(!isRecord(t))return{value:t,changed:!1};let e=!1,r=t;if("string"==typeof t.type&&"text"===t.type&&"string"==typeof t.text){const n=stripTransportMetadata(t.text);n!==t.text&&(r={...r,text:n},e=!0)}if(Array.isArray(t.content)){const n=sanitizeNode(t.content);n.changed&&(e||(r={...r}),r.content=n.value,e=!0)}return{value:e?r:t,changed:e}}export function sanitizePromptPayload(t){return sanitizeNode(t).value}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beeos-claw",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
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>",
|
|
@@ -39,10 +39,12 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@beeos-ai/bridge-client": "^0.1.0",
|
|
42
|
+
"adm-zip": "^0.5.17",
|
|
42
43
|
"centrifuge": "^5.5.3",
|
|
43
44
|
"ws": "^8.18.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
47
|
+
"@types/adm-zip": "^0.5.8",
|
|
46
48
|
"@types/node": "^22.0.0",
|
|
47
49
|
"@types/ws": "^8.18.1",
|
|
48
50
|
"terser": "^5.39.0",
|