beeos-claw 0.6.48-staging.2 → 0.6.48-staging.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/a2a/tools.js CHANGED
@@ -1 +1 @@
1
- import{agentFetch}from"../agent-auth.js";import{resolveAgentIdFromMentions}from"../delegation/mention-resolver.js";async function a2aFetch(t,e,a){const n=`${t.agentGatewayUrl}${e}`;return t.agentKeys?agentFetch(n,t.agentKeys,a):fetch(n,a)}export{a2aFetch};function normalizeState(t){if(!t)return"";const e=t.toLowerCase();return{task_state_submitted:"submitted",task_state_delivered:"submitted",task_state_working:"working",task_state_completed:"completed",task_state_failed:"failed",task_state_timeout:"timeout",task_state_canceled:"canceled",task_state_rejected:"rejected",task_state_input_required:"input-required",task_state_auth_required:"auth-required"}[e]??e}function unwrapTask(t){if(!t||"object"!=typeof t)return;const e=t,a=e.task;return a&&"object"==typeof a?a:e}function extractArtifactText(t){const e=t?.artifacts;if(!e)return;return e.flatMap(t=>t.parts??[]).filter(t=>"string"==typeof t.text&&(void 0===t.kind||"text"===t.kind)).map(t=>t.text).join("\n")||void 0}function isRecord(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function stringValue(t){return"string"==typeof t&&""!==t?t:void 0}function normalizeArtifactFilePart(t){const e=stringValue(t.kind),a=isRecord(t.file)?t.file:void 0;let n=stringValue(t.url),s=stringValue(t.filename),r=stringValue(t.mediaType);if(a&&(n=stringValue(a.uri)??stringValue(a.url)??n,s=stringValue(a.name)??stringValue(a.filename)??s,r=stringValue(a.mimeType)??stringValue(a.mediaType)??r),n&&(!e||"file"===e))return{uri:n,...s?{name:s}:{},...r?{mimeType:r}:{}}}function extractArtifactFiles(t){const e=t?.artifacts;if(!e)return;const a=[];for(const t of e){const e=t.parts;if(Array.isArray(e))for(const t of e){if(!isRecord(t))continue;const e=normalizeArtifactFilePart(t);e&&a.push(e)}}return a.length>0?a:void 0}function extractStatusText(t){const e=t?.status,a=e?.message?.parts,n=a?.[0]?.text;return"string"==typeof n?n:void 0}function taskState(t){return normalizeState(t?.status?.state)}const TERMINAL_FAILURE_STATES=new Set(["failed","timeout","canceled","rejected"]);function taskContextId(t){const e=t?.contextId;return"string"==typeof e&&""!==e?e:void 0}function buildTaskPayload(t,e,a){const n=taskState(t),s=extractArtifactText(t),r=extractArtifactFiles(t),i=extractStatusText(t),o=taskContextId(t),c={status:n,is_terminal:"completed"===n||TERMINAL_FAILURE_STATES.has(n)};return e&&(c.task_id=e),a&&(c.agent_id=a),o&&(c.context_id=o),s?c.result=s:i&&(c.error=i),r&&(c.files=r),c}export{unwrapTask,extractArtifactText,extractArtifactFiles,extractStatusText,taskState,taskContextId,normalizeState,buildTaskPayload};export class TaskHandleRegistry{seq=0;handles=new Map;taskIdToHandle=new Map;create(t,e,a,n){const s="bth_"+ ++this.seq;return this.handles.set(s,{taskId:t,agentId:e,contextId:a,sessionKey:n}),this.taskIdToHandle.set(t,s),s}resolveTaskId(t){const e=this.handles.get(t);return e?e.taskId:t}resolveAgentId(t){const e=this.handles.get(t);if(e)return e.agentId;const a=this.taskIdToHandle.get(t);return a?this.handles.get(a)?.agentId:void 0}}const defaultRegistry=new TaskHandleRegistry;export function registerA2ATools(t,e,a=defaultRegistry,n,s,r,i){t.registerTool({name:"beeos_discover_agents",description:"List agents available for A2A collaboration. When called without a query, returns all visible agents (your own agents + public agents). When called with a query, filters by name, description, or skill. Results include an `isSelf` flag marking the agent that represents the current instance (do not call yourself). Each result includes `instanceName` (human-readable device/instance label) when available.",parameters:{type:"object",properties:{query:{type:"string",description:"Optional keyword to filter agents by name, description, or skill. Omit to list all agents."}},required:[]},async execute(t,a,n){const{query:s}=a??{};try{const t=new URLSearchParams({limit:"50"});s&&t.set("query",s);const a=await a2aFetch(e,`/api/v1/a2a/discover?${t}`,{signal:n});if(!a.ok)return{content:JSON.stringify({error:`discover failed: ${a.status}`})};const r=await a.json(),o=i?.();if(o&&Array.isArray(r?.data))for(const t of r.data)t.isSelf=t.instance_id===o;return{content:JSON.stringify(r,null,2)}}catch(t){if(n?.aborted)throw t;return{content:JSON.stringify({error:t instanceof Error?t.message:String(t)})}}}}),t.registerTool({name:"beeos_call_agent",description:"Ask another agent to perform a task. This creates the task ASYNCHRONOUSLY and returns immediately with a handle ({ task_id, agent_id, context_id, status }) — it does NOT wait for the result. To fetch the agent's reply, call beeos_get_task with the returned task_id (and agent_id). Do NOT call beeos_call_agent again to poll an existing task — that creates a brand-new task.",parameters:{type:"object",properties:{agentId:{type:"string",description:"The target agent UUID (e.g. d04080d1-...) or display name. Prefer the UUID from discover results or @mention metadata when available."},message:{type:"string",description:"The task description or message to send to the agent"},taskId:{type:"string",description:"Optional existing task ID to continue a multi-turn conversation. When provided, the message is appended to the existing task instead of creating a new one."},files:{type:"array",items:{type:"string"},description:"Optional file URLs to include with the request"},contextId:{type:"string",description:"Optional context ID for multi-turn conversations with the same agent"},timeoutMs:{type:"number",description:"Server-side task timeout in milliseconds (how long the target agent may run before the task is timed out). Defaults to 600000 (10 minutes). This is NOT a client wait time — beeos_call_agent returns immediately regardless."}},required:["agentId","message"]},async execute(t,i,o){let{agentId:c,message:d,files:l,contextId:u,timeoutMs:g,taskId:f}=i;const h=n?.(t),p=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;if(!p.test(c)){const a=s?.(t);if(a?.length){const t=resolveAgentIdFromMentions(a,c);t&&(e.logger.info?.(`beeos_call_agent: resolved "${c}" → ${t} via mention context`),c=t)}if(!p.test(c))try{const t=await a2aFetch(e,`/api/v1/a2a/discover?query=${encodeURIComponent(c)}&limit=10`,{signal:o});if(t.ok){const a=await t.json(),n=a?.data??[],s=c.toLowerCase(),r=n.find(t=>t.name.toLowerCase()===s)??n.find(t=>t.name.toLowerCase().includes(s));if(!r)return{content:JSON.stringify({error:`Could not resolve agent name "${c}". Available agents: ${n.map(t=>`${t.name} (${t.id})`).join(", ")||"none"}`})};e.logger.info?.(`beeos_call_agent: resolved "${c}" → ${r.id} (${r.name}) via discover`),c=r.id}}catch{}}const m=u||(h?`session:${h}`:""),y=g||6e5;try{const t={jsonrpc:"2.0",id:`claw-${Date.now()}`,method:"SendMessage",params:{message:{role:"user",parts:[{text:d}],...m?{contextId:m}:{},...f?{taskId:f}:{}},configuration:{timeoutMs:y}}},n=`/api/v1/a2a/${encodeURIComponent(c)}`,s=AbortSignal.timeout(3e4),i=o?AbortSignal.any([o,s]):s,l=await a2aFetch(e,n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),signal:i}),g=await l.text();let p;try{p=JSON.parse(g)}catch{p={}}if(!l.ok||p.error){if(401===l.status||403===l.status){const t=p.error??{};-32600===t.code&&r&&r("_beeos/auth_required",{type:"auth_required",target_agent_id:c,target_agent_name:c,title:"Authentication Required",content:`Agent **${c}** requires authentication.`})}const t=p.error?JSON.stringify(p.error):g;return{content:JSON.stringify({error:`send task failed: ${l.status} ${t}`,agent_id:c})}}const k=unwrapTask(p.result),_=k?.id;return _?(a.create(_,c,u,h),{content:JSON.stringify({status:taskState(k)||"working",task_id:_,agent_id:c,context_id:taskContextId(k)??m??void 0,message:`Task created. Call beeos_get_task with task_id "${_}" (and agent_id "${c}") to fetch status and result.`})}):{content:JSON.stringify({error:"task created but server returned no task_id",agent_id:c})}}catch(t){if(o?.aborted)throw t;return t instanceof DOMException&&"TimeoutError"===t.name?{content:JSON.stringify({error:"create task request timed out after 30000ms",agent_id:c})}:{content:JSON.stringify({error:t instanceof Error?t.message:String(t),agent_id:c})}}}}),t.registerTool({name:"beeos_get_task",description:'Fetch the current state AND data of an existing A2A task by its task_id. Use this to check on a task that beeos_call_agent created (it returns status "working" with a task_id), or to re-read a finished task. Always returns a JSON object { status, task_id, agent_id, context_id, is_terminal, result?, files?, error? }: `result` holds the produced output (present whenever the device produced output, EVEN for timeout/canceled/failed tasks that still produced content); `files` contains structured file artifacts ({uri,name,mimeType}). Do not output MEDIA:, and do not upload/fetch/browser/shell files[].uri for display. Use beeos_fetch_file only for local byte analysis. `error` holds the failure reason when there is no produced output; `is_terminal` indicates whether the task has reached a final state. Do NOT call beeos_call_agent again to re-query an existing task — that creates a brand-new task.',parameters:{type:"object",properties:{taskId:{type:"string",description:"The task ID returned by a previous beeos_call_agent call."},agentId:{type:"string",description:"The target agent UUID that owns the task (the same agentId passed to beeos_call_agent). Optional when the task was created earlier in this session — it is resolved automatically."}},required:["taskId"]},async execute(t,n,s){const{taskId:r,agentId:i}=n??{};if(!r)return{content:JSON.stringify({error:"taskId is required"})};const o=i||a.resolveAgentId(r);if(!o)return{content:JSON.stringify({error:`agentId is required to query task "${r}" (it was not created in this session, so the target agent is unknown). Pass the agentId you originally called.`,task_id:r})};try{const t=`/api/v1/a2a/${encodeURIComponent(o)}`,a=await a2aFetch(e,t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:`claw-get-${Date.now()}`,method:"GetTask",params:{id:r}}),signal:s}),n=await a.text();let i;try{i=JSON.parse(n)}catch{i={}}if(!a.ok||i.error){const t=i.error?JSON.stringify(i.error):n;return{content:JSON.stringify({error:`get task failed: ${a.status} ${t}`,task_id:r,agent_id:o})}}const c=unwrapTask(i.result);return{content:JSON.stringify(buildTaskPayload(c,r,o))}}catch(t){if(s?.aborted)throw t;return{content:JSON.stringify({error:t instanceof Error?t.message:String(t),task_id:r,agent_id:o})}}}})}
1
+ import{agentFetch}from"../agent-auth.js";import{resolveAgentIdFromMentions}from"../delegation/mention-resolver.js";async function a2aFetch(t,e,a){const n=`${t.agentGatewayUrl}${e}`;return t.agentKeys?agentFetch(n,t.agentKeys,a):fetch(n,a)}export{a2aFetch};export async function completeA2ATask(t,e,a,n){const s=await a2aFetch(t,`/api/v1/a2a/tasks/${encodeURIComponent(e)}/complete`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({result:n?"":a,error:n})});if(!s.ok)throw new Error(`A2A CompleteTask failed: ${s.status}`)}function normalizeState(t){if(!t)return"";const e=t.toLowerCase();return{task_state_submitted:"submitted",task_state_delivered:"submitted",task_state_working:"working",task_state_completed:"completed",task_state_failed:"failed",task_state_timeout:"timeout",task_state_canceled:"canceled",task_state_rejected:"rejected",task_state_input_required:"input-required",task_state_auth_required:"auth-required"}[e]??e}function unwrapTask(t){if(!t||"object"!=typeof t)return;const e=t,a=e.task;return a&&"object"==typeof a?a:e}function extractArtifactText(t){const e=t?.artifacts;if(!e)return;return e.flatMap(t=>t.parts??[]).filter(t=>"string"==typeof t.text&&(void 0===t.kind||"text"===t.kind)).map(t=>t.text).join("\n")||void 0}function isRecord(t){return"object"==typeof t&&null!==t&&!Array.isArray(t)}function stringValue(t){return"string"==typeof t&&""!==t?t:void 0}function normalizeArtifactFilePart(t){const e=stringValue(t.kind),a=isRecord(t.file)?t.file:void 0;let n=stringValue(t.url),s=stringValue(t.filename),r=stringValue(t.mediaType);if(a&&(n=stringValue(a.uri)??stringValue(a.url)??n,s=stringValue(a.name)??stringValue(a.filename)??s,r=stringValue(a.mimeType)??stringValue(a.mediaType)??r),n&&(!e||"file"===e))return{uri:n,...s?{name:s}:{},...r?{mimeType:r}:{}}}function extractArtifactFiles(t){const e=t?.artifacts;if(!e)return;const a=[];for(const t of e){const e=t.parts;if(Array.isArray(e))for(const t of e){if(!isRecord(t))continue;const e=normalizeArtifactFilePart(t);e&&a.push(e)}}return a.length>0?a:void 0}function extractStatusText(t){const e=t?.status,a=e?.message?.parts,n=a?.[0]?.text;return"string"==typeof n?n:void 0}function taskState(t){return normalizeState(t?.status?.state)}const TERMINAL_FAILURE_STATES=new Set(["failed","timeout","canceled","rejected"]);function taskContextId(t){const e=t?.contextId;return"string"==typeof e&&""!==e?e:void 0}function buildTaskPayload(t,e,a){const n=taskState(t),s=extractArtifactText(t),r=extractArtifactFiles(t),i=extractStatusText(t),o=taskContextId(t),c={status:n,is_terminal:"completed"===n||TERMINAL_FAILURE_STATES.has(n)};return e&&(c.task_id=e),a&&(c.agent_id=a),o&&(c.context_id=o),s?c.result=s:i&&(c.error=i),r&&(c.files=r),c}export{unwrapTask,extractArtifactText,extractArtifactFiles,extractStatusText,taskState,taskContextId,normalizeState,buildTaskPayload};export class TaskHandleRegistry{seq=0;handles=new Map;taskIdToHandle=new Map;create(t,e,a,n){const s="bth_"+ ++this.seq;return this.handles.set(s,{taskId:t,agentId:e,contextId:a,sessionKey:n}),this.taskIdToHandle.set(t,s),s}resolveTaskId(t){const e=this.handles.get(t);return e?e.taskId:t}resolveAgentId(t){const e=this.handles.get(t);if(e)return e.agentId;const a=this.taskIdToHandle.get(t);return a?this.handles.get(a)?.agentId:void 0}}const defaultRegistry=new TaskHandleRegistry;export function registerA2ATools(t,e,a=defaultRegistry,n,s,r,i){t.registerTool({name:"beeos_discover_agents",description:"List agents available for A2A collaboration. When called without a query, returns all visible agents (your own agents + public agents). When called with a query, filters by name, description, or skill. Results include an `isSelf` flag marking the agent that represents the current instance (do not call yourself). Each result includes `instanceName` (human-readable device/instance label) when available.",parameters:{type:"object",properties:{query:{type:"string",description:"Optional keyword to filter agents by name, description, or skill. Omit to list all agents."}},required:[]},async execute(t,a,n){const{query:s}=a??{};try{const t=new URLSearchParams({limit:"50"});s&&t.set("query",s);const a=await a2aFetch(e,`/api/v1/a2a/discover?${t}`,{signal:n});if(!a.ok)return{content:JSON.stringify({error:`discover failed: ${a.status}`})};const r=await a.json(),o=i?.();if(o&&Array.isArray(r?.data))for(const t of r.data)t.isSelf=t.instance_id===o;return{content:JSON.stringify(r,null,2)}}catch(t){if(n?.aborted)throw t;return{content:JSON.stringify({error:t instanceof Error?t.message:String(t)})}}}}),t.registerTool({name:"beeos_call_agent",description:"Ask another agent to perform a task. This creates the task ASYNCHRONOUSLY and returns immediately with a handle ({ task_id, agent_id, context_id, status }) — it does NOT wait for the result. To fetch the agent's reply, call beeos_get_task with the returned task_id (and agent_id). Do NOT call beeos_call_agent again to poll an existing task — that creates a brand-new task.",parameters:{type:"object",properties:{agentId:{type:"string",description:"The target agent UUID (e.g. d04080d1-...) or display name. Prefer the UUID from discover results or @mention metadata when available."},message:{type:"string",description:"The task description or message to send to the agent"},taskId:{type:"string",description:"Optional existing task ID to continue a multi-turn conversation. When provided, the message is appended to the existing task instead of creating a new one."},files:{type:"array",items:{type:"string"},description:"Optional file URLs to include with the request"},contextId:{type:"string",description:"Optional context ID for multi-turn conversations with the same agent"},timeoutMs:{type:"number",description:"Server-side task timeout in milliseconds (how long the target agent may run before the task is timed out). Defaults to 600000 (10 minutes). This is NOT a client wait time — beeos_call_agent returns immediately regardless."}},required:["agentId","message"]},async execute(t,i,o){let{agentId:c,message:d,files:l,contextId:u,timeoutMs:g,taskId:f}=i;const p=n?.(t),h=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;if(!h.test(c)){const a=s?.(t);if(a?.length){const t=resolveAgentIdFromMentions(a,c);t&&(e.logger.info?.(`beeos_call_agent: resolved "${c}" → ${t} via mention context`),c=t)}if(!h.test(c))try{const t=await a2aFetch(e,`/api/v1/a2a/discover?query=${encodeURIComponent(c)}&limit=10`,{signal:o});if(t.ok){const a=await t.json(),n=a?.data??[],s=c.toLowerCase(),r=n.find(t=>t.name.toLowerCase()===s)??n.find(t=>t.name.toLowerCase().includes(s));if(!r)return{content:JSON.stringify({error:`Could not resolve agent name "${c}". Available agents: ${n.map(t=>`${t.name} (${t.id})`).join(", ")||"none"}`})};e.logger.info?.(`beeos_call_agent: resolved "${c}" → ${r.id} (${r.name}) via discover`),c=r.id}}catch{}}const m=u||(p?`session:${p}`:""),y=g||6e5;try{const t={jsonrpc:"2.0",id:`claw-${Date.now()}`,method:"SendMessage",params:{message:{role:"user",parts:[{text:d}],...m?{contextId:m}:{},...f?{taskId:f}:{}},configuration:{timeoutMs:y}}},n=`/api/v1/a2a/${encodeURIComponent(c)}`,s=AbortSignal.timeout(3e4),i=o?AbortSignal.any([o,s]):s,l=await a2aFetch(e,n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),signal:i}),g=await l.text();let h;try{h=JSON.parse(g)}catch{h={}}if(!l.ok||h.error){if(401===l.status||403===l.status){const t=h.error??{};-32600===t.code&&r&&r("_beeos/auth_required",{type:"auth_required",target_agent_id:c,target_agent_name:c,title:"Authentication Required",content:`Agent **${c}** requires authentication.`})}const t=h.error?JSON.stringify(h.error):g;return{content:JSON.stringify({error:`send task failed: ${l.status} ${t}`,agent_id:c})}}const k=unwrapTask(h.result),_=k?.id;return _?(a.create(_,c,u,p),{content:JSON.stringify({status:taskState(k)||"working",task_id:_,agent_id:c,context_id:taskContextId(k)??m??void 0,message:`Task created. Call beeos_get_task with task_id "${_}" (and agent_id "${c}") to fetch status and result.`})}):{content:JSON.stringify({error:"task created but server returned no task_id",agent_id:c})}}catch(t){if(o?.aborted)throw t;return t instanceof DOMException&&"TimeoutError"===t.name?{content:JSON.stringify({error:"create task request timed out after 30000ms",agent_id:c})}:{content:JSON.stringify({error:t instanceof Error?t.message:String(t),agent_id:c})}}}}),t.registerTool({name:"beeos_get_task",description:'Fetch the current state AND data of an existing A2A task by its task_id. Use this to check on a task that beeos_call_agent created (it returns status "working" with a task_id), or to re-read a finished task. Always returns a JSON object { status, task_id, agent_id, context_id, is_terminal, result?, files?, error? }: `result` holds the produced output (present whenever the device produced output, EVEN for timeout/canceled/failed tasks that still produced content); `files` contains structured file artifacts ({uri,name,mimeType}). Do not output MEDIA:, and do not upload/fetch/browser/shell files[].uri for display. Use beeos_fetch_file only for local byte analysis. `error` holds the failure reason when there is no produced output; `is_terminal` indicates whether the task has reached a final state. Do NOT call beeos_call_agent again to re-query an existing task — that creates a brand-new task.',parameters:{type:"object",properties:{taskId:{type:"string",description:"The task ID returned by a previous beeos_call_agent call."},agentId:{type:"string",description:"The target agent UUID that owns the task (the same agentId passed to beeos_call_agent). Optional when the task was created earlier in this session — it is resolved automatically."}},required:["taskId"]},async execute(t,n,s){const{taskId:r,agentId:i}=n??{};if(!r)return{content:JSON.stringify({error:"taskId is required"})};const o=i||a.resolveAgentId(r);if(!o)return{content:JSON.stringify({error:`agentId is required to query task "${r}" (it was not created in this session, so the target agent is unknown). Pass the agentId you originally called.`,task_id:r})};try{const t=`/api/v1/a2a/${encodeURIComponent(o)}`,a=await a2aFetch(e,t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:`claw-get-${Date.now()}`,method:"GetTask",params:{id:r}}),signal:s}),n=await a.text();let i;try{i=JSON.parse(n)}catch{i={}}if(!a.ok||i.error){const t=i.error?JSON.stringify(i.error):n;return{content:JSON.stringify({error:`get task failed: ${a.status} ${t}`,task_id:r,agent_id:o})}}const c=unwrapTask(i.result);return{content:JSON.stringify(buildTaskPayload(c,r,o))}}catch(t){if(s?.aborted)throw t;return{content:JSON.stringify({error:t instanceof Error?t.message:String(t),task_id:r,agent_id:o})}}}})}
@@ -1 +1 @@
1
- import{extractChatPrompt}from"@beeos-ai/message-sdk/chat-envelope";import{buildMentionInstruction,parseMentionsParam}from"./delegation/mention-resolver.js";import{dispatchImInboundPrompt}from"./im-runtime-dispatch.js";import{promptRunKey}from"./runtime/prompt-run-store.js";const MAX_PROTOCOL_MARKER_LEN=64,DEFAULT_ORIGIN="im",resolveOrigin=e=>"string"!=typeof e||0===e.length||e.length>64?"im":e,conversationIdFromMessage=e=>"string"==typeof e.conversationId?e.conversationId:"";export const createImInboundHandler=e=>{const{runtime:n,pluginConfig:t,client:s,localServer:o,promptRunStore:a,sessionMutationCoordinator:r,logger:i,writeObs:c,principalIdOverride:d,promptTimeoutMs:p,dispatch:l=dispatchImInboundPrompt,activeRunAuthority:m}=e;return{onChatMessage:async e=>{const g=e.content??{},h=g.metadata??void 0,u="string"==typeof h?.target_agent_id?h.target_agent_id:"",_=u?o.resolveLocalAgentId?.(u):void 0;if(!u||!_)return void i.warn?.(`[beeos-claw] chat_message target_agent_id=${u||"missing"} not resolvable to a local agent — drop`);const I=extractChatPrompt(e,{source:"chat_message",localAgentId:_});if(!I)return;if(!I.channelId||!I.messageId)return void i.warn?.(`[beeos-claw] chat_message missing channelId/messageId (sessionKey=${I.sessionKey}) — drop`);const y=d||o.getDefaultPlatformAgent()?.instanceId||"";if(!y)return void i.warn?.(`[beeos-claw] chat_message dropped (no principalId resolved, sessionKey=${I.sessionKey})`);const f="string"==typeof h?.delivery_principal?h.delivery_principal:"";if(f&&f!==y)return void i.info?.(`[beeos-claw] chat_message skipped (delivery_principal=${f}, selfPrincipalId=${y}, message_id=${I.messageId})`);const v=parseMentionsParam(g.mentions);o.ensureImInboundSession(I.sessionKey,I.channelId,I.messageId,v);const $=buildMentionInstruction(v),b=$?`${$}\n${I.message}`:I.message,w=resolveOrigin(h?.protocol);i.debug?.(`[beeos-claw] chat_message dispatch (sessionKey=${I.sessionKey}, channelId=${I.channelId}, origin=${w})`);const M=e=>{c({component:"plugin",domain:"im_dispatch",name:`im.dispatch.${e.type}`,severity:"dispatch_error"===e.type?"warn":"info",payload:e})};try{await l({runtime:n,pluginConfig:t,prompt:{channelId:I.channelId,inboundMessageId:I.messageId,selfPrincipalId:y,dispatchInput:{message:b,attachments:I.files},sessionKey:I.sessionKey},client:s,logger:i,promptRunStore:a,sessionMutationCoordinator:r,abortTransport:o,origin:w,promptTimeoutMs:p,onLifecycleEvent:M,activeRunAuthority:m})}catch(e){i.error?.(`[beeos-claw] chat_message dispatch failed channelId=${I.channelId} in_reply_to=${I.messageId} error_code=runtime_dispatch_failed`)}},onChatCancel:async e=>{const n=e.content??{},t="string"==typeof n.target_message_id?n.target_message_id:"",s="string"==typeof n.reason?n.reason:"unknown";if(!t)return void i.warn?.(`[beeos-claw] chat_cancel dropped (no target_message_id, reason=${s})`);const o=conversationIdFromMessage(e);if(!o)return void i.warn?.(`[beeos-claw] chat_cancel dropped (no conversation identity, target_message_id=${t}, reason=${s})`);const r=`chat_cancel:${s}`,c=await a.abort(promptRunKey(o,t),r);c.ok?i.info?.(`[beeos-claw] chat_cancel accepted (target_message_id=${t}, requestId=${c.requestId}, channelId=${c.channelId}, reason=${s})`):i.info?.(`[beeos-claw] chat_cancel no-op (target_message_id=${t}, reason=${s}, why=${c.reason})`)}}};
1
+ import{extractChatPrompt}from"@beeos-ai/message-sdk/chat-envelope";import{buildMentionInstruction,parseMentionsParam}from"./delegation/mention-resolver.js";import{dispatchImInboundPrompt}from"./im-runtime-dispatch.js";import{promptRunKey}from"./runtime/prompt-run-store.js";const MAX_PROTOCOL_MARKER_LEN=64,DEFAULT_ORIGIN="im",resolveOrigin=e=>"string"!=typeof e||0===e.length||e.length>64?"im":e,conversationIdFromMessage=e=>"string"==typeof e.conversationId?e.conversationId:"";export const createImInboundHandler=e=>{const{runtime:n,pluginConfig:t,client:s,localServer:o,promptRunStore:a,sessionMutationCoordinator:i,logger:r,writeObs:c,principalIdOverride:d,promptTimeoutMs:p,dispatch:l=dispatchImInboundPrompt,completeA2ATask:m,activeRunAuthority:g}=e;return{onChatMessage:async e=>{const u=e.content??{},h=u.metadata??void 0,I="string"==typeof h?.target_agent_id?h.target_agent_id:"",_=I?o.resolveLocalAgentId?.(I):void 0;if(!I||!_)return void r.warn?.(`[beeos-claw] chat_message target_agent_id=${I||"missing"} not resolvable to a local agent — drop`);const y=extractChatPrompt(e,{source:"chat_message",localAgentId:_});if(!y)return;if(!y.channelId||!y.messageId)return void r.warn?.(`[beeos-claw] chat_message missing channelId/messageId (sessionKey=${y.sessionKey}) — drop`);const f=d||o.getDefaultPlatformAgent()?.instanceId||"";if(!f)return void r.warn?.(`[beeos-claw] chat_message dropped (no principalId resolved, sessionKey=${y.sessionKey})`);const v="string"==typeof h?.delivery_principal?h.delivery_principal:"";if(v&&v!==f)return void r.info?.(`[beeos-claw] chat_message skipped (delivery_principal=${v}, selfPrincipalId=${f}, message_id=${y.messageId})`);const $=parseMentionsParam(u.mentions);o.ensureImInboundSession(y.sessionKey,y.channelId,y.messageId,$);const b=buildMentionInstruction($),w=b?`${b}\n${y.message}`:y.message,M=resolveOrigin(h?.protocol),K=y.messageId,R="a2a"===M&&m?(e,n)=>m(K,e,n):void 0;r.debug?.(`[beeos-claw] chat_message dispatch (sessionKey=${y.sessionKey}, channelId=${y.channelId}, origin=${M})`);const A=e=>{c({component:"plugin",domain:"im_dispatch",name:`im.dispatch.${e.type}`,severity:"dispatch_error"===e.type?"warn":"info",payload:e})};try{await l({runtime:n,pluginConfig:t,prompt:{channelId:y.channelId,inboundMessageId:y.messageId,selfPrincipalId:f,dispatchInput:{message:w,attachments:y.files},sessionKey:y.sessionKey},client:s,logger:r,promptRunStore:a,sessionMutationCoordinator:i,abortTransport:o,origin:M,promptTimeoutMs:p,onLifecycleEvent:A,...R?{onTerminalResult:R}:{},activeRunAuthority:g})}catch(e){r.error?.(`[beeos-claw] chat_message dispatch failed channelId=${y.channelId} in_reply_to=${y.messageId} error_code=runtime_dispatch_failed`)}},onChatCancel:async e=>{const n=e.content??{},t="string"==typeof n.target_message_id?n.target_message_id:"",s="string"==typeof n.reason?n.reason:"unknown";if(!t)return void r.warn?.(`[beeos-claw] chat_cancel dropped (no target_message_id, reason=${s})`);const o=conversationIdFromMessage(e);if(!o)return void r.warn?.(`[beeos-claw] chat_cancel dropped (no conversation identity, target_message_id=${t}, reason=${s})`);const i=`chat_cancel:${s}`,c=await a.abort(promptRunKey(o,t),i);c.ok?r.info?.(`[beeos-claw] chat_cancel accepted (target_message_id=${t}, requestId=${c.requestId}, channelId=${c.channelId}, reason=${s})`):r.info?.(`[beeos-claw] chat_cancel no-op (target_message_id=${t}, reason=${s}, why=${c.reason})`)}}};
@@ -1 +1 @@
1
- import{createHash,randomUUID}from"node:crypto";import{dispatchRuntimeReplyWithRoute,hasRuntimeReplyApi}from"./runtime/reply-router.js";import{createImReplySink}from"./runtime/im-reply-sink.js";import{SerializedCallbackQueue}from"./runtime/callback-queue.js";import{promptRunKey}from"./runtime/prompt-run-store.js";import{classifyAgentRuntimeFailure}from"./runtime/agent-runtime-failure-classifier.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"./runtime/session-mutation-coordinator.js";import{interceptAgentRuntimeFailures,messageStopReasonForRuntimeFailure,SESSION_BUSY_PUBLIC_MESSAGE}from"./runtime/im-runtime-failure-policy.js";import{maybeRenameImConversationTitle}from"./runtime/conversation-title.js";const normalizePromptTimeoutMs=e=>{if("number"!=typeof e||!Number.isFinite(e))return;const n=Math.max(0,Math.trunc(e));return n>0?n:void 0},ABORT_TERMINAL_GRACE_MS=1e4,formatErrMessage=e=>{if(e instanceof Error)return e.message;if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}},failMessageStream=async(e,n)=>{void 0!==n.body&&e.setBody(n.body),await e.fail(void 0===n.stopReason?void 0:{stopReason:n.stopReason})};export const deterministicReplyMessageId=(e,n,t,a)=>`${e}_${createHash("sha256").update(`${n}:${t}:${a}`).digest("hex").slice(0,32)}`;export const dispatchImInboundPrompt=async e=>{const{runtime:n,prompt:t,client:a,logger:r,promptRunStore:i,sessionMutationCoordinator:s,abortTransport:o,activeRunAuthority:c}=e;if(!hasRuntimeReplyApi(n))return r.warn?.(`[im] runtime dispatch skipped reason=reply_api_unavailable channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_api_unavailable",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),!1;const d=`im_req_${randomUUID().replace(/-/g,"")}`,l=randomUUID(),u=Date.now(),m=e.origin??"im",p={protocol:"im",requestId:d,sessionId:t.sessionKey??"agent:main",sourceMessageId:t.inboundMessageId,chatId:t.channelId,origin:m},I=normalizePromptTimeoutMs(e.promptTimeoutMs),g=promptRunKey(t.channelId,t.inboundMessageId),h=t.sessionKey??"agent:main",y=new AbortController;let _,f,R;const w=new Promise((e,n)=>{R=n});let E;const b=()=>{void 0!==E&&(clearTimeout(E),E=void 0)},M=async e=>{if(f??=e,y.abort(e),!_)return{ok:!0};const n=await s.requestCancel(_,o);return n.ok?n.terminalObservation.then(e=>{e.ok||R(new Error(SESSION_RECOVERY_REQUIRED_CODE))}):R(new Error(SESSION_RECOVERY_REQUIRED_CODE)),{ok:n.ok}};if(!i.register(g,{requestId:d,channelId:t.channelId,requestCancel:M,startedAt:u,sessionKey:h}))return r.warn?.(`[im] runtime dispatch rejected duplicate inboundMessageId=${t.inboundMessageId} requestId=${d} channel=${t.channelId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"duplicate_inbound_message_id",cause:"abort"}),!1;const $=deterministicReplyMessageId("asst",t.channelId,t.inboundMessageId,t.selfPrincipalId),S=a.messages.startStream({conversationId:t.channelId,clientMessageId:$,idempotencyKey:$,replyTo:e.inReplyTo??t.inboundMessageId,type:"agent_reply",content:{}});let v,q=!1;try{q="duplicate"===(await S.opened()).outcome}catch(n){let s="unconfirmed";try{s=(await a.messages.getEnvelope(t.channelId,$)).state}catch{}return r.warn?.(`[im] runtime reply stream open outcome unknown requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} reply_id=${$} reconciled_state=${s}: ${formatErrMessage(n)}; runtime_not_invoked`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_open_outcome_unknown",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(g),!1}if(q)return r.info?.(`[im] runtime dispatch skipped existing deterministic reply requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"idempotent_reply_exists",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(g),!1;const k=new SerializedCallbackQueue,C=createImReplySink({stream:S,openReasoningStream:()=>{const n=deterministicReplyMessageId("rsn",t.channelId,t.inboundMessageId,t.selfPrincipalId);return v=a.messages.startStream({conversationId:t.channelId,clientMessageId:n,idempotencyKey:n,replyTo:e.inReplyTo??t.inboundMessageId,type:"agent_reasoning",content:{}}),v},abortSignal:y.signal,callbackQueue:k,logger:r,requestId:d,runId:l,channelId:t.channelId,inboundMessageId:t.inboundMessageId,origin:m,onAgentRunStart:e=>{s.bindRun(_,e)}}),O=interceptAgentRuntimeFailures(C,"im_runtime",e=>r.warn?.(`[im] runtime failure reply suppressed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} error_code=${e.errorCode}`)),T=O.sink,A=async(e,n)=>{if(!v)return!1;try{return"cancel"===e?await v.cancel({stopReason:"user_stop"}):"fail"===e?await v.fail(n?{stopReason:n}:void 0):await v.finalize({stopReason:"end_turn"}),!1}catch(e){return r.warn?.(`[im] runtime reasoning stream terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`),!0}};let D,U,K=!1;const N=async e=>{T.dispatcher.markComplete(e);try{await T.dispatcher.waitForIdle()}catch{}},F=()=>{b();const n=normalizePromptTimeoutMs(e.abortTerminalGraceMs)??1e4;E=setTimeout(()=>{E=void 0,(async()=>{if(S.isTerminated)return;const e=y.signal.reason??f,n=void 0!==U,a=!n&&"string"==typeof e&&e.startsWith("chat_cancel");if(await N("failure"),!S.isTerminated)try{a?(await A("cancel"),await S.cancel({stopReason:"user_stop"})):n?(await A("fail","timeout"),await failMessageStream(S,{body:U?.message??"IM runtime dispatch timed out",stopReason:"timeout"})):(await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"})),r.warn?.(`[im] abort settlement watchdog terminalized reply requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} user_cancel=${a} timeout=${n}`)}catch(e){r.warn?.(`[im] abort settlement watchdog terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`)}})()},n)};y.signal.aborted?F():y.signal.addEventListener("abort",F,{once:!0});const L=()=>{if(!y.signal.aborted)return;const e=y.signal.reason;throw e instanceof Error?e:new Error(String(e??"runtime dispatch aborted"))};let Q,P,Y;const G=async e=>{if(!_)throw new Error("session_mutation_not_acquired");const n=await s.converge(_,e);return n.ok?(Q=n.permit,!0):(r.error?.(`[im] session mutation recovery required requestId=${d} sessionKey=${h} reason=${n.recovery.reason}`),P={code:n.recovery.code,reason:n.recovery.reason,sessionKey:n.recovery.sessionKey,operationId:n.recovery.operationId,surface:"message_service",...n.recovery.terminalKind?{terminalKind:n.recovery.terminalKind}:{}},!1)};try{L();const o=s.tryStartInteractive({sessionKey:h,label:"im_chat_message"},"message_service");if(!o.ok){try{await failMessageStream(S,{body:SESSION_BUSY_PUBLIC_MESSAGE,stopReason:"error"})}catch(e){r.warn?.(`[im] session busy terminal write failed channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return r.warn?.(`[im] session write gate busy sessionKey=${h} channel=${t.channelId} in_reply_to=${t.inboundMessageId} active_operation=${o.activeOperationId??"unknown"}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"session_busy",cause:"runtime_error"}),!1}if(_=o.token,s.bindRun(_,l),c){const e=s.snapshot(h);Y={runtimeConversationUid:t.channelId,runtimeSessionKey:t.channelId,historyGeneration:c.historyGeneration(t.channelId),targetMessageId:t.inboundMessageId,runtimeRunId:l,activeRunRegistrationId:`im:${e?.operationId??l}`},c.activeRuns.register(Y,async()=>{const e=`chat_cancel:activeRun:${Y.activeRunRegistrationId}`,n=await i.abort(g,e);return n.ok||"already_aborted"===n.reason?"accepted":"terminal_committing"===n.reason||"not_found"===n.reason?"already_terminal":"recovery_required"})}L(),r.info?.(`[im] runtime dispatching requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} origin=${m}`),e.onLifecycleEvent?.({type:"dispatch_start",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId});const y=dispatchRuntimeReplyWithRoute({runtime:n,pluginConfig:e.pluginConfig,preparedPrompt:t.dispatchInput,replyRoute:p,replySinks:{im:T},onDispatchInvoked:()=>{K=!0}});void 0!==I&&(D=setTimeout(()=>{U=new Error(`IM runtime dispatch timed out after ${I}ms`),r.warn?.(`[im] runtime dispatch timeout requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} timeout_ms=${I}`),M(U)},I));const f=Promise.race([y,w]),R=await f.finally(()=>{D&&(clearTimeout(D),D=void 0)}),E=O.detectedFailure();if(E)throw new Error(E.diagnosticMessage);if(L(),await N("success"),L(),!i.claimTerminal(g,"completion")){if(L(),"cancelling"===i.phase(g))throw new Error("cancellation in progress");throw new Error("terminal outcome already claimed")}if(!await G("completed"))return await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:"runtime_error",...P?{recovery:P}:{}}),!1;const b=T.dispatcher.getTerminalErrorText?.();if(void 0!==b){await A("fail");let n=!1;try{await failMessageStream(S,{body:b,stopReason:"error"})}catch(e){n=!0,r.warn?.(`[im] runtime reply stream error-terminal write outcome unknown requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return c&&Y&&c.activeRuns.markTerminal(Y,"failed"),r.warn?.(`[im] runtime dispatch ended with an error terminal requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(R.counts)} terminalWriteFailed=${n} elapsed_ms=${Date.now()-u}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"runtime_error_terminal_reply",cause:"runtime_error"}),!1}const $=await A("complete");let v=!1;try{await S.finalize({stopReason:"end_turn"})}catch(e){v=!0,r.warn?.(`[im] runtime reply stream finalize outcome unknown requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}c&&Y&&c.activeRuns.markTerminal(Y,"completed"),r.info?.(`[im] runtime dispatch completed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(R.counts)} elapsed_ms=${Date.now()-u}`),e.onLifecycleEvent?.({type:"dispatch_complete",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,counts:R.counts,...v?{terminalWriteFailed:!0}:{},...$?{reasoningTerminalWriteFailed:!0}:{}});const q="string"==typeof t.dispatchInput.message?t.dispatchInput.message:"",k=T.dispatcher.getAccumulatedBody?.()??"";return maybeRenameImConversationTitle({client:a,conversationId:t.channelId,question:q,answer:k,logger:r}),!0}catch(n){await N("failure");const a=y.signal.aborted,s=a?y.signal.reason:void 0,o="cancelling"===i.phase(g),l=void 0!==U,u=s??f,m=!l&&"string"==typeof u&&u.startsWith("chat_cancel");let p="runtime_error";l?p="timeout":(a||o)&&(p="abort");const I=o||m||l||a?"cancellation":"completion";if(!i.claimTerminal(g,I)){if(r.warn?.(`[im] terminal claim raced requestId=${d} channel=${t.channelId}; best-effort envelope terminal if still streaming`),!S.isTerminated)try{m?(await A("cancel"),await S.cancel({stopReason:"user_stop"})):l?(await A("fail","timeout"),await failMessageStream(S,{body:U?.message??formatErrMessage(n),stopReason:"timeout"})):(await A("fail"),await failMessageStream(S,{body:formatErrMessage(n),stopReason:"error"}))}catch(e){r.warn?.(`[im] raced terminal write failed requestId=${d}: ${formatErrMessage(e)}`)}throw n}const h=m?"cancelled":l?"timeout":"error";if(c&&Y&&c.activeRuns.markTerminal(Y,"cancelled"===h?"cancelled":"failed"),_&&!await G(h))throw m?(await A("cancel"),await S.cancel({stopReason:"user_stop"}).catch(()=>{})):(await A("fail"),await failMessageStream(S,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{})),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:p,...P?{recovery:P}:{}}),n;const R=classifyAgentRuntimeFailure({raw:n,source:"im_runtime",runtimeKind:"openclaw",requestAborted:m}),w=m?R:O.detectedFailure()??R;m?await A("cancel"):l?await A("fail","timeout"):await A("fail");try{m?await S.cancel({stopReason:"user_stop"}):await failMessageStream(S,{body:w.publicMessage,stopReason:messageStopReasonForRuntimeFailure(w)})}catch(e){r.warn?.(`[im] runtime stream terminal write failed requestId=${d} channel=${t.channelId}: ${formatErrMessage(e)}`)}throw r.warn?.(`[im] runtime dispatch failed requestId=${d} channel=${t.channelId} in_reply_to=${t.inboundMessageId} cause=${p} dispatchInvoked=${K} error_code=${w.errorCode}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:w.errorCode,cause:p}),n}finally{b(),i.unregister(g),K?Q&&s.release(Q):s.releaseBeforeRuntime(_)}};
1
+ import{createHash,randomUUID}from"node:crypto";import{dispatchRuntimeReplyWithRoute,hasRuntimeReplyApi}from"./runtime/reply-router.js";import{createImReplySink}from"./runtime/im-reply-sink.js";import{SerializedCallbackQueue}from"./runtime/callback-queue.js";import{promptRunKey}from"./runtime/prompt-run-store.js";import{classifyAgentRuntimeFailure}from"./runtime/agent-runtime-failure-classifier.js";import{SESSION_RECOVERY_REQUIRED_CODE,SESSION_RECOVERY_REQUIRED_MESSAGE}from"./runtime/session-mutation-coordinator.js";import{interceptAgentRuntimeFailures,messageStopReasonForRuntimeFailure,SESSION_BUSY_PUBLIC_MESSAGE}from"./runtime/im-runtime-failure-policy.js";import{maybeRenameImConversationTitle}from"./runtime/conversation-title.js";const normalizePromptTimeoutMs=e=>{if("number"!=typeof e||!Number.isFinite(e))return;const n=Math.max(0,Math.trunc(e));return n>0?n:void 0},ABORT_TERMINAL_GRACE_MS=1e4,formatErrMessage=e=>{if(e instanceof Error)return e.message;if("string"==typeof e)return e;try{return JSON.stringify(e)}catch{return String(e)}},failMessageStream=async(e,n)=>{void 0!==n.body&&e.setBody(n.body),await e.fail(void 0===n.stopReason?void 0:{stopReason:n.stopReason})};export const deterministicReplyMessageId=(e,n,a,t)=>`${e}_${createHash("sha256").update(`${n}:${a}:${t}`).digest("hex").slice(0,32)}`;export const dispatchImInboundPrompt=async e=>{const{runtime:n,prompt:a,client:t,logger:r,promptRunStore:i,sessionMutationCoordinator:s,abortTransport:o,activeRunAuthority:c}=e;if(!hasRuntimeReplyApi(n))return r.warn?.(`[im] runtime dispatch skipped reason=reply_api_unavailable channel=${a.channelId} in_reply_to=${a.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_api_unavailable",channelId:a.channelId,inboundMessageId:a.inboundMessageId}),!1;const d=`im_req_${randomUUID().replace(/-/g,"")}`,l=randomUUID(),u=Date.now(),m=e.origin??"im",p={protocol:"im",requestId:d,sessionId:a.sessionKey??"agent:main",sourceMessageId:a.inboundMessageId,chatId:a.channelId,origin:m},I=normalizePromptTimeoutMs(e.promptTimeoutMs),g=async(n,t)=>{if(e.onTerminalResult)try{await e.onTerminalResult(n,t)}catch(e){r.warn?.(`[im] terminal business callback failed requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}`)}},h=promptRunKey(a.channelId,a.inboundMessageId),y=a.sessionKey??"agent:main",_=new AbortController;let f,R,w;const E=new Promise((e,n)=>{w=n});let b;const M=()=>{void 0!==b&&(clearTimeout(b),b=void 0)},$=async e=>{if(R??=e,_.abort(e),!f)return{ok:!0};const n=await s.requestCancel(f,o);return n.ok?n.terminalObservation.then(e=>{e.ok||w(new Error(SESSION_RECOVERY_REQUIRED_CODE))}):w(new Error(SESSION_RECOVERY_REQUIRED_CODE)),{ok:n.ok}};if(!i.register(h,{requestId:d,channelId:a.channelId,requestCancel:$,startedAt:u,sessionKey:y}))return r.warn?.(`[im] runtime dispatch rejected duplicate inboundMessageId=${a.inboundMessageId} requestId=${d} channel=${a.channelId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:"duplicate_inbound_message_id",cause:"abort"}),!1;const S=deterministicReplyMessageId("asst",a.channelId,a.inboundMessageId,a.selfPrincipalId),v=t.messages.startStream({conversationId:a.channelId,clientMessageId:S,idempotencyKey:S,replyTo:e.inReplyTo??a.inboundMessageId,type:"agent_reply",content:{}});let q,k=!1;try{k="duplicate"===(await v.opened()).outcome}catch(n){let s="unconfirmed";try{s=(await t.messages.getEnvelope(a.channelId,S)).state}catch{}return r.warn?.(`[im] runtime reply stream open outcome unknown requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} reply_id=${S} reconciled_state=${s}: ${formatErrMessage(n)}; runtime_not_invoked`),e.onLifecycleEvent?.({type:"skipped",reason:"reply_open_outcome_unknown",channelId:a.channelId,inboundMessageId:a.inboundMessageId}),i.unregister(h),!1}if(k)return r.info?.(`[im] runtime dispatch skipped existing deterministic reply requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"idempotent_reply_exists",channelId:a.channelId,inboundMessageId:a.inboundMessageId}),i.unregister(h),!1;const C=new SerializedCallbackQueue,O=createImReplySink({stream:v,openReasoningStream:()=>{const n=deterministicReplyMessageId("rsn",a.channelId,a.inboundMessageId,a.selfPrincipalId);return q=t.messages.startStream({conversationId:a.channelId,clientMessageId:n,idempotencyKey:n,replyTo:e.inReplyTo??a.inboundMessageId,type:"agent_reasoning",content:{}}),q},abortSignal:_.signal,callbackQueue:C,logger:r,requestId:d,runId:l,channelId:a.channelId,inboundMessageId:a.inboundMessageId,origin:m,onAgentRunStart:e=>{s.bindRun(f,e)}}),T=interceptAgentRuntimeFailures(O,"im_runtime",e=>r.warn?.(`[im] runtime failure reply suppressed requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} error_code=${e.errorCode}`)),A=T.sink,D=async(e,n)=>{if(!q)return!1;try{return"cancel"===e?await q.cancel({stopReason:"user_stop"}):"fail"===e?await q.fail(n?{stopReason:n}:void 0):await q.finalize({stopReason:"end_turn"}),!1}catch(e){return r.warn?.(`[im] runtime reasoning stream terminal write failed requestId=${d} channel=${a.channelId}: ${formatErrMessage(e)}`),!0}};let U,K,N=!1;const F=async e=>{A.dispatcher.markComplete(e);try{await A.dispatcher.waitForIdle()}catch{}},L=()=>{M();const n=normalizePromptTimeoutMs(e.abortTerminalGraceMs)??1e4;b=setTimeout(()=>{b=void 0,(async()=>{if(v.isTerminated)return;const e=_.signal.reason??R,n=void 0!==K,t=!n&&"string"==typeof e&&e.startsWith("chat_cancel");if(await F("failure"),!v.isTerminated)try{t?(await D("cancel"),await v.cancel({stopReason:"user_stop"})):n?(await D("fail","timeout"),await failMessageStream(v,{body:K?.message??"IM runtime dispatch timed out",stopReason:"timeout"})):(await D("fail"),await failMessageStream(v,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"})),r.warn?.(`[im] abort settlement watchdog terminalized reply requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} user_cancel=${t} timeout=${n}`)}catch(e){r.warn?.(`[im] abort settlement watchdog terminal write failed requestId=${d} channel=${a.channelId}: ${formatErrMessage(e)}`)}})()},n)};_.signal.aborted?L():_.signal.addEventListener("abort",L,{once:!0});const Q=()=>{if(!_.signal.aborted)return;const e=_.signal.reason;throw e instanceof Error?e:new Error(String(e??"runtime dispatch aborted"))};let P,Y,G;const j=async e=>{if(!f)throw new Error("session_mutation_not_acquired");const n=await s.converge(f,e);return n.ok?(P=n.permit,!0):(r.error?.(`[im] session mutation recovery required requestId=${d} sessionKey=${y} reason=${n.recovery.reason}`),Y={code:n.recovery.code,reason:n.recovery.reason,sessionKey:n.recovery.sessionKey,operationId:n.recovery.operationId,surface:"message_service",...n.recovery.terminalKind?{terminalKind:n.recovery.terminalKind}:{}},!1)};try{Q();const o=s.tryStartInteractive({sessionKey:y,label:"im_chat_message"},"message_service");if(!o.ok){try{await failMessageStream(v,{body:SESSION_BUSY_PUBLIC_MESSAGE,stopReason:"error"})}catch(e){r.warn?.(`[im] session busy terminal write failed channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}`)}return r.warn?.(`[im] session write gate busy sessionKey=${y} channel=${a.channelId} in_reply_to=${a.inboundMessageId} active_operation=${o.activeOperationId??"unknown"}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:"session_busy",cause:"runtime_error"}),!1}if(f=o.token,s.bindRun(f,l),c){const e=s.snapshot(y);G={runtimeConversationUid:a.channelId,runtimeSessionKey:a.channelId,historyGeneration:c.historyGeneration(a.channelId),targetMessageId:a.inboundMessageId,runtimeRunId:l,activeRunRegistrationId:`im:${e?.operationId??l}`},c.activeRuns.register(G,async()=>{const e=`chat_cancel:activeRun:${G.activeRunRegistrationId}`,n=await i.abort(h,e);return n.ok||"already_aborted"===n.reason?"accepted":"terminal_committing"===n.reason||"not_found"===n.reason?"already_terminal":"recovery_required"})}Q(),r.info?.(`[im] runtime dispatching requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} origin=${m}`),e.onLifecycleEvent?.({type:"dispatch_start",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId});const _=dispatchRuntimeReplyWithRoute({runtime:n,pluginConfig:e.pluginConfig,preparedPrompt:a.dispatchInput,replyRoute:p,replySinks:{im:A},onDispatchInvoked:()=>{N=!0}});void 0!==I&&(U=setTimeout(()=>{K=new Error(`IM runtime dispatch timed out after ${I}ms`),r.warn?.(`[im] runtime dispatch timeout requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} timeout_ms=${I}`),$(K)},I));const R=Promise.race([_,E]),w=await R.finally(()=>{U&&(clearTimeout(U),U=void 0)}),b=T.detectedFailure();if(b)throw new Error(b.diagnosticMessage);if(Q(),await F("success"),Q(),!i.claimTerminal(h,"completion")){if(Q(),"cancelling"===i.phase(h))throw new Error("cancellation in progress");throw new Error("terminal outcome already claimed")}if(!await j("completed"))return await D("fail"),await failMessageStream(v,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:"runtime_error",...Y?{recovery:Y}:{}}),!1;const M=A.dispatcher.getTerminalErrorText?.();if(void 0!==M){await D("fail");let n=!1;try{await failMessageStream(v,{body:M,stopReason:"error"})}catch(e){n=!0,r.warn?.(`[im] runtime reply stream error-terminal write outcome unknown requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}`)}return c&&G&&c.activeRuns.markTerminal(G,"failed"),r.warn?.(`[im] runtime dispatch ended with an error terminal requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} counts=${JSON.stringify(w.counts)} terminalWriteFailed=${n} elapsed_ms=${Date.now()-u}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:"runtime_error_terminal_reply",cause:"runtime_error"}),n||await g("",M),!1}const S=await D("complete");let q=!1;try{await v.finalize({stopReason:"end_turn"})}catch(e){q=!0,r.warn?.(`[im] runtime reply stream finalize outcome unknown requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId}: ${formatErrMessage(e)}`)}c&&G&&c.activeRuns.markTerminal(G,"completed"),r.info?.(`[im] runtime dispatch completed requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} counts=${JSON.stringify(w.counts)} elapsed_ms=${Date.now()-u}`),e.onLifecycleEvent?.({type:"dispatch_complete",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,counts:w.counts,...q?{terminalWriteFailed:!0}:{},...S?{reasoningTerminalWriteFailed:!0}:{}});const k=A.dispatcher.getAccumulatedBody?.()??"";q||await g(k,"");const C="string"==typeof a.dispatchInput.message?a.dispatchInput.message:"";return maybeRenameImConversationTitle({client:t,conversationId:a.channelId,question:C,answer:k,logger:r}),!0}catch(n){await F("failure");const t=_.signal.aborted,s=t?_.signal.reason:void 0,o="cancelling"===i.phase(h),l=void 0!==K,u=s??R,m=!l&&"string"==typeof u&&u.startsWith("chat_cancel");let p="runtime_error";l?p="timeout":(t||o)&&(p="abort");const I=o||m||l||t?"cancellation":"completion";if(!i.claimTerminal(h,I)){if(r.warn?.(`[im] terminal claim raced requestId=${d} channel=${a.channelId}; best-effort envelope terminal if still streaming`),!v.isTerminated)try{m?(await D("cancel"),await v.cancel({stopReason:"user_stop"})):l?(await D("fail","timeout"),await failMessageStream(v,{body:K?.message??formatErrMessage(n),stopReason:"timeout"})):(await D("fail"),await failMessageStream(v,{body:formatErrMessage(n),stopReason:"error"}))}catch(e){r.warn?.(`[im] raced terminal write failed requestId=${d}: ${formatErrMessage(e)}`)}throw n}const y=m?"cancelled":l?"timeout":"error";if(c&&G&&c.activeRuns.markTerminal(G,"cancelled"===y?"cancelled":"failed"),f&&!await j(y))throw m?(await D("cancel"),await v.cancel({stopReason:"user_stop"}).catch(()=>{})):(await D("fail"),await failMessageStream(v,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{})),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:p,...Y?{recovery:Y}:{}}),n;const w=classifyAgentRuntimeFailure({raw:n,source:"im_runtime",runtimeKind:"openclaw",requestAborted:m}),E=m?w:T.detectedFailure()??w;m?await D("cancel"):l?await D("fail","timeout"):await D("fail");let b=!1;try{m?await v.cancel({stopReason:"user_stop"}):await failMessageStream(v,{body:E.publicMessage,stopReason:messageStopReasonForRuntimeFailure(E)})}catch(e){b=!0,r.warn?.(`[im] runtime stream terminal write failed requestId=${d} channel=${a.channelId}: ${formatErrMessage(e)}`)}throw r.warn?.(`[im] runtime dispatch failed requestId=${d} channel=${a.channelId} in_reply_to=${a.inboundMessageId} cause=${p} dispatchInvoked=${N} error_code=${E.errorCode}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:d,channelId:a.channelId,inboundMessageId:a.inboundMessageId,error:E.errorCode,cause:p}),m||b||await g("",E.publicMessage),n}finally{M(),i.unregister(h),N?P&&s.release(P):s.releaseBeforeRuntime(f)}};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{hasRuntimeReplyApi}from"./runtime/reply-router.js";import{PromptRunStore}from"./runtime/prompt-run-store.js";import{SessionWriteGate}from"./acp-gateway/session-write-gate.js";import{OpenClawSessionWriteQuiescence}from"./runtime/openclaw-session-write-quiescence.js";import{SessionMutationCoordinator}from"./runtime/session-mutation-coordinator.js";import{scheduleProcessRuntimeRestart}from"./runtime/runtime-restart-scheduler.js";import{createImInboundHandler}from"./im-inbound-handler.js";import{createTokenProvider}from"@beeos-ai/message-sdk/auth";import{BeeosClawMessageClientProcessOwner}from"./message-client-composition.js";import{MessageProjectionInboundConsumer}from"./message-projection-inbound-consumer.js";import{MessageClientTerminalStateRecovery}from"./runtime/message-client-terminal-state-recovery.js";import{RuntimeAuthorityMessageBusLifecycle}from"./runtime/runtime-authority-message-bus-lifecycle.js";import{FileInboundExecutionJournal}from"./inbound-execution-journal.js";import{registerA2ATools,TaskHandleRegistry}from"./a2a/tools.js";import{BEEOS_UPLOAD_TOOL_NAME,BEEOS_UPLOAD_TOOL_SCHEMA,BEEOS_UPLOAD_TOOL_DESCRIPTION,executeUpload}from"./upload/tool.js";import{BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME,BEEOS_DESKTOP_SCREENSHOT_TOOL_SCHEMA,BEEOS_DESKTOP_SCREENSHOT_TOOL_DESCRIPTION,executeDesktopScreenshot}from"./upload/desktop-screenshot-tool.js";import{BEEOS_LIST_FILES_TOOL_NAME,BEEOS_LIST_FILES_TOOL_SCHEMA,BEEOS_LIST_FILES_TOOL_DESCRIPTION,executeListFiles}from"./files/list-tool.js";import{BEEOS_FETCH_FILE_TOOL_NAME,BEEOS_FETCH_FILE_TOOL_SCHEMA,BEEOS_FETCH_FILE_TOOL_DESCRIPTION,executeFetchFile}from"./files/fetch-tool.js";import{writeUploadToolResult,cleanupUploadToolResultCache}from"./upload/result-cache.js";import{handleFileStage}from"./upload/file-stage.js";import{handleStatusRequest}from"./status.js";import{ACPServer}from"./acp-server.js";import{createObsEventWriter}from"./observability.js";import{resolvePluginVersion}from"./plugin-version.js";import{createTerminalWebSocketService}from"./terminal/websocket.js";import{TerminalManager}from"./terminal/session-manager.js";import{loadAgentKeyPair,agentBoundFetch,agentFetch}from"./agent-auth.js";import{readDiscoveryCache,writeDiscoveryCache}from"./discovery-cache.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{setUploadDeps}from"./upload/upload-file.js";import{registerLocalFileExtractor}from"./upload/local-file-extractors.js";import{browserScreenshotExtractor}from"./browser/screenshot-extractor.js";import{BEEOS_FILE_RESOURCE_ROUTING_RULE}from"./files/beeos-file-uri.js";import{CanvasStateManager}from"./canvas/state.js";import{CanvasRelayClient}from"./canvas/relay-client.js";import{RENDER_UI_TOOL_NAME,RENDER_UI_TOOL_DESCRIPTION,RENDER_UI_TOOL_SCHEMA,CLOSE_UI_TOOL_NAME,CLOSE_UI_TOOL_DESCRIPTION,CLOSE_UI_TOOL_SCHEMA,createRenderUiHandler,createCloseUiHandler,CANVAS_SYSTEM_PROMPT}from"./canvas/tools.js";import{FormSchemaRegistry}from"./surface/form-schema-registry.js";import{buildCronSchemas}from"./surface/cron-schemas.js";import{RENDER_FORM_TOOL_NAME,RENDER_FORM_TOOL_SCHEMA,buildRenderFormDescription,createRenderFormHandler}from"./surface/render-form-tool.js";import{ResourceDescriptorRegistry}from"./surface/resource-descriptor-registry.js";import{CRON_RESOURCE}from"./surface/cron-resource.js";import{generateActionTools}from"./surface/action-tool-generator.js";import{buildThirdPartyChannelSet,isThirdPartyChannel}from"./channel-detection.js";import{registerArouterMediaProviders}from"./media/index.js";import{homedir}from"node:os";import{join}from"node:path";import{RuntimeProductionAuthority}from"./runtime/runtime-production-authority.js";import{FileRuntimeCommandJournal}from"./runtime/runtime-command-journal.js";import{FileRuntimePoisonDeliveryJournal}from"./runtime/runtime-poison-delivery-journal.js";import{InProcessMcpToolBridge}from"./acp-gateway/mcp-tool-bridge.js";import{LocalEncryptedCredentialReferenceVault}from"./acp-gateway/mcp-credential-reference-port.js";import{AgentGatewayGrantJwksReadinessAdapter,RuntimeServiceTransportReadinessAdapter,createRuntimeServiceProductionCandidate}from"./runtime/runtime-service-production-candidate.js";import{MessageServiceCronDeliveryHttpAuthority}from"./runtime/message-service-cron-delivery-http-authority.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null,sharedCanvasState=null,sharedAcpServer=null;const sharedSessionWriteGate=new SessionWriteGate,sharedPromptRunStore=new PromptRunStore;let sharedSessionMutationCoordinator,sharedRuntimeAuthority,sharedMcpToolBridge,sharedMcpCredentialVault;const sharedMessageClientOwner=new BeeosClawMessageClientProcessOwner;let sharedActiveMessageConsumer,sharedInboundExecutionJournal;const sharedTerminalManager=new TerminalManager;let _initializing=!1,_pluginRegisteredAt=0;const ED25519_PKCS8_PREFIX=Buffer.from("302e020100300506032b657004220420","hex");function agentKeyPairToIdentity(e){return{publicKeyBase64:()=>Buffer.from(e.publicKey).toString("base64"),sign:async r=>{const{createPrivateKey:t,sign:o}=await import("node:crypto"),n=t({key:32===e.privateKey.length?Buffer.concat([ED25519_PKCS8_PREFIX,Buffer.from(e.privateKey)]):Buffer.from(e.privateKey),format:"der",type:"pkcs8"});return new Uint8Array(o(null,Buffer.from(r),n))}}}let unhandledRejectionObserverInstalled=!1;function installUnhandledRejectionObserver(e,r,t){unhandledRejectionObserverInstalled||(unhandledRejectionObserverInstalled=!0,process.on("unhandledRejection",t=>{const o=t instanceof Error?t.message:String(t),n=t instanceof Error?t.stack:void 0;e.logger.error?.(`[beeos-claw] unhandledRejection: ${o}${n?`\n${n}`:""}`),r({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:o,error:{code:"unhandled_rejection",message:o,...n?{stack:n}:{}}})}),process.on("uncaughtException",t=>{const o=t instanceof Error?t.message:String(t);e.logger.error?.(`[beeos-claw] uncaughtException: ${t instanceof Error?t.stack??t.message:String(t)}`),r({component:"plugin",domain:"fatal",name:"uncaught_exception",severity:"error",summary:o,error:{code:"uncaught_exception",message:o}})}))}function initializePlugin(e){let r,t;try{r=e.runtime?.config?.loadConfig?.()}catch{}try{t=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:r})}catch(r){return e.logger.error?.(`[beeos-claw] Failed to resolve config: ${r instanceof Error?r.message:String(r)}`),null}const o=t.config,n=buildThirdPartyChannelSet(r);n.size>0&&e.logger.info?.(`[beeos-claw] Third-party channels detected: ${[...n].join(", ")}`);const a=o.log.enabled?createObsEventWriter({dir:o.log.dir,redaction:{gatewayToken:o.gateway.token},logger:e.logger}):()=>{};a({component:"plugin",domain:"lifecycle",name:"lifecycle.startup",severity:"info",payload:{pluginVersion:PLUGIN_VERSION,pid:process.pid,nodeVersion:process.version,cwd:process.cwd()}}),a({component:"plugin",domain:"config",name:"config.resolved",severity:"info",payload:{config:o,sources:t.sources,openclawConfigPath:t.openclawConfigPath}});for(const r of t.validation)a({component:"plugin",domain:"config",name:"config.validation",severity:r.severity,summary:r.message,error:{code:r.code,message:r.message,nextSteps:r.nextSteps}}),"error"===r.severity?e.logger.error?.(`[beeos-claw] ${r.message}`):e.logger.warn?.(`[beeos-claw] ${r.message}`);if(t.validation.some(e=>"error"===e.severity)&&e.logger.error?.("[beeos-claw] Plugin has configuration errors — some features may not work."),o.keyFile)try{agentKeys=loadAgentKeyPair(o.keyFile);const r=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${r})`)}catch(r){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${r}`),e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no key pair — signed requests disabled)`)}else e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no keyFile configured)`);return agentKeys&&o.agentGatewayUrl&&(setUploadDeps({gatewayUrl:o.agentGatewayUrl,keys:agentKeys,logger:e.logger}),registerLocalFileExtractor(browserScreenshotExtractor),e.logger.info?.("[beeos-claw] Upload deps + browser screenshot extractor registered")),{cfg:o,writeObs:a,thirdPartyChannels:n,openclawConfig:r}}function registerUploadTools(e,r,t){const{cfg:o,writeObs:n}=r;cleanupUploadToolResultCache().catch(r=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(r)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(r,t,a){e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} params=${JSON.stringify(t)}`);const i=await executeUpload(t,o,agentKeys,a);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} ok=${i.ok}`),n({component:"upload-tool",domain:"tool",name:i.ok?"upload.success":"upload.failure",severity:i.ok?"info":"warn",payload:{toolCallId:r,ok:i.ok,fileCount:i.files?.length??0,error:i.error}}),(async t=>{try{await writeUploadToolResult(r,BEEOS_UPLOAD_TOOL_NAME,t),await cleanupUploadToolResultCache()}catch(t){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${r}: ${String(t)}`)}return t})(i)}}),e.registerTool({name:BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME,description:BEEOS_DESKTOP_SCREENSHOT_TOOL_DESCRIPTION,parameters:BEEOS_DESKTOP_SCREENSHOT_TOOL_SCHEMA,async execute(r,t,a){e.logger.debug?.(`[beeos_desktop_screenshot] toolCallId=${r} params=${JSON.stringify(t)}`);const i=await executeDesktopScreenshot(t,o,agentKeys,a);return e.logger.debug?.(`[beeos_desktop_screenshot] toolCallId=${r} ok=${i.ok}`),n({component:"desktop-screenshot-tool",domain:"tool",name:i.ok?"desktop_screenshot.success":"desktop_screenshot.failure",severity:i.ok?"info":"warn",payload:{toolCallId:r,ok:i.ok,path:i.path,fileCount:i.files?.length??0,error:i.error}}),(async t=>{try{await writeUploadToolResult(r,BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME,t),await cleanupUploadToolResultCache()}catch(t){e.logger.warn?.(`[beeos_desktop_screenshot] persist cache failed toolCallId=${r}: ${String(t)}`)}return t})(i)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,t){await handleFileStage(e,t,r.cfg)}}),e.registerHttpRoute({path:"/beeos/status",auth:"plugin",handler(e,r){handleStatusRequest(e,r,{publicKey:agentKeys?.publicKey??null,pluginVersion:PLUGIN_VERSION})}}),e.registerTool({name:BEEOS_LIST_FILES_TOOL_NAME,description:BEEOS_LIST_FILES_TOOL_DESCRIPTION,parameters:BEEOS_LIST_FILES_TOOL_SCHEMA,async execute(r,t,a){e.logger.debug?.(`[beeos_list_files] toolCallId=${r} params=${JSON.stringify(t)}`);const i=await executeListFiles(t,o,agentKeys,a);return n({component:"file-list-tool",domain:"tool",name:i.ok?"list_files.success":"list_files.failure",severity:i.ok?"info":"warn",payload:{toolCallId:r,ok:i.ok,fileCount:i.files?.length??0,error:i.error}}),i}}),e.registerTool({name:BEEOS_FETCH_FILE_TOOL_NAME,description:BEEOS_FETCH_FILE_TOOL_DESCRIPTION,parameters:BEEOS_FETCH_FILE_TOOL_SCHEMA,async execute(r,t,a){e.logger.debug?.(`[beeos_fetch_file] toolCallId=${r} params=${JSON.stringify(t)}`);const i=await executeFetchFile(t,o,agentKeys,{signal:a,resolvePath:e.resolvePath});return n({component:"file-fetch-tool",domain:"tool",name:i.ok?"fetch_file.success":"fetch_file.failure",severity:i.ok?"info":"warn",payload:{toolCallId:r,ok:i.ok,path:i.path,size:i.size,error:i.error}}),i}})}function registerServices(e,r,t){const{cfg:o,writeObs:n,thirdPartyChannels:a}=r,i=agentKeys?(e,r)=>agentFetch(e,agentKeys,r):void 0,s=agentKeys?(e,r)=>agentBoundFetch(e,agentKeys,r):void 0,l=agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):void 0,c=join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos"),d=sharedInboundExecutionJournal??=new FileInboundExecutionJournal(join(c,"inbound-execution-journal.v1.jsonl"));sharedMcpToolBridge??=new InProcessMcpToolBridge,!sharedMcpCredentialVault&&agentKeys&&l&&(sharedMcpCredentialVault=new LocalEncryptedCredentialReferenceVault(join(c,"mcp-credential-vault.json"),l,agentKeys.privateKey)),sharedMcpToolBridge.registerTools(e),!sharedRuntimeAuthority&&agentKeys&&o.agentGatewayUrl&&i&&s&&(sharedRuntimeAuthority=new RuntimeProductionAuthority({stateDirectory:c,agentGatewayUrl:o.agentGatewayUrl,agentFetch:i,boundAgentFetch:s,keys:agentKeys,logger:e.logger})),sharedSessionMutationCoordinator??=new SessionMutationCoordinator(sharedSessionWriteGate,new OpenClawSessionWriteQuiescence(e.logger,{agentId:o.gateway.agentId||"main"}),e.logger,{onRecoveryRequired:r=>{scheduleProcessRuntimeRestart(r,e.logger)}});const g=sharedSessionMutationCoordinator;sharedAcpServer||(sharedAcpServer=new ACPServer(o,e.logger,g,n,i,l,a,sharedTerminalManager,sharedRuntimeAuthority,void 0,sharedMcpToolBridge,sharedMcpCredentialVault,s));const u=sharedAcpServer;t.current=u,u.nudgeMcpNativeProjection();const p=sharedPromptRunStore,{enabled:m,principalId:y,a2aEnabled:f}=o.messageBus;let h;o.agentGatewayUrl&&agentKeys&&(h=createTokenProvider({agentGatewayUrl:o.agentGatewayUrl,identity:agentKeyPairToIdentity(agentKeys)}));const w=process.env.MESSAGE_RUNTIME_DELIVERY_KEY?.trim(),E=y||process.env.BEEOS_PRINCIPAL_ID||"",_=h?sharedMessageClientOwner.configure({tokenProvider:h,identityId:E,...o.messageBus.serviceUrl?{serviceUrl:o.messageBus.serviceUrl}:{},logger:e.logger,...sharedRuntimeAuthority&&w?{runtimeDelivery:{scopedDeliveryKey:w,authority:{currentLease:()=>{const e=sharedRuntimeAuthority?.authorityLease,r=sharedRuntimeAuthority?.currentConsumerBinding();return e&&r?{...e,journalStoreId:r.journalStoreId,journalGeneration:r.journalGeneration}:null}}}}:{}}):void 0,v=sharedRuntimeAuthority,b=o.messageBus.serviceUrl;if(_&&v&&b&&w){const e=_.nodeTransport;u.setCronConversationDelivery({deliver:async r=>{const t=v.authorityLease;if(!t)throw new Error("current runtime authority lease unavailable for cron delivery");const o=new MessageServiceCronDeliveryHttpAuthority({messageServiceUrl:b,fetchAuthorized:async(r,o)=>await e.fetchRuntimeChannel(r,{runtimeLeaseCredential:t.runtimeLeaseCredential},o)});return await o.deliver({...r,runtimeLeaseCredential:t.runtimeLeaseCredential})}})}else u.setCronConversationDelivery(void 0);e.registerService({id:"beeos-acp",async start(){const r=Date.now(),t=_pluginRegisteredAt?r-_pluginRegisteredAt:-1;e.logger.info?.(`[beeos-claw] beeos-acp service start() begin, sincePluginRegister=${t}ms, bridge.url=${o.bridge.url||"(empty)"}`);const n=await createRuntimeServiceProductionCandidate({...o.agentGatewayUrl&&i?{authorizationAdapter:new AgentGatewayGrantJwksReadinessAdapter({agentGatewayUrl:o.agentGatewayUrl})}:{},..._&&o.agentGatewayUrl&&s&&w?{transportAdapter:new RuntimeServiceTransportReadinessAdapter({agentGatewayUrl:o.agentGatewayUrl,messageService:_.nodeTransport,messageClient:_.client,configuredServiceUrl:o.messageBus.serviceUrl,boundAgentFetch:s,scopedDeliveryKey:w})}:{},commandJournal:sharedRuntimeAuthority?.journal??new FileRuntimeCommandJournal(join(c,"runtime-command-journal.json")),poisonJournal:new FileRuntimePoisonDeliveryJournal(join(c,"runtime-poison-deliveries.json")),onConsumerDeliveryError:r=>{const t=r instanceof Error?`${r.name}: ${r.message}${"status"in r?` status=${r.status}`:""}${"code"in r?` code=${r.code}`:""}\n${r.stack??""}`:String(r);e.logger.error?.(`[runtime-service] delivery worker failed before acknowledgement; delivery retained for retry: ${t}`)},onConsumerLeaseStarvation:({starvedForMs:r,recovered:t})=>{const o=Math.round(r/1e3);t?e.logger.info?.(`[runtime-service] runtime authority lease restored after ${o}s without delivery reads`):e.logger.warn?.(`[runtime-service] no runtime authority lease for ${o}s; delivery reads suspended, queued runtime operations (model switch, session control) will not be consumed`)},onConsumerRequestFailure:r=>e.logger.error?.(`[runtime-service] request failed operationId=${r.operationId} method=${r.method}: ${r.error instanceof Error?r.error.stack??r.error.message:String(r.error)}`)});if(sharedRuntimeAuthority?.setRuntimeServiceMethods("ready"===n.status?n.serviceMethods:[]),sharedRuntimeAuthority?.setRuntimeResourceBindingContractRevision("ready"===n.status?n.resourceBindingContractRevision:void 0),u.setRuntimeRpcConsumerFactory("ready"===n.status?n.factory:void 0),"unavailable"===n.status?e.logger.warn?.("[runtime-service] capability fenced: "+n.blockers.map(e=>e.id).join(",")):n.capabilityBlockers.length&&e.logger.warn?.("[runtime-service] method capability fenced: "+n.capabilityBlockers.map(e=>e.method).join(",")),!o.bridge.url&&o.agentGatewayUrl&&i){const r=Date.now(),t=await discoverBridgeFromGW(o.agentGatewayUrl,i);if(e.logger.info?.(`[beeos-claw] bridge discovery took ${Date.now()-r}ms result=${t?.url??"null"}`),t)o.bridge.url=t.url,writeDiscoveryCache({bridge:{url:t.url,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${t.url}`);else{const r=readDiscoveryCache();r?.bridge?.url?(o.bridge.url=r.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${r.bridge.url}`)):e.logger.debug?.("[beeos-claw] Bridge URL not available (no GW, no cache) — bridge disabled")}}await u.start(o.bridge.url||void 0),e.logger.info?.(`[beeos-claw] ACP Server started (total service start: ${Date.now()-r}ms)`)},async stop(){await u.stop(),e.logger.info?.("[beeos-claw] ACP Server stopped")}});if(m&&void 0!==h&&h){const r=sharedMessageClientOwner.createRegistrationToken(),t=async(e,r)=>{const t=await h(e);if(!t.identityId||t.identityId!==e)throw new Error("Message SDK token identity did not match the current runtime authority");return{principalId:e,runtimeEpoch:r}},a=(e,r,t)=>({getLocalAgentId:()=>r,resolveLocalAgentId:e=>e===t?r:void 0,getDefaultPlatformAgent:()=>({instanceId:e.principalId}),ensureImInboundSession:(e,r,t,o)=>{u.ensureImInboundSession(e,r,t,o)},chatAbort:(e,r)=>u.chatAbort(e,r)}),i=sharedRuntimeAuthority?new RuntimeAuthorityMessageBusLifecycle({authority:sharedRuntimeAuthority,logger:e.logger,bus:{connect:async i=>{await sharedMessageClientOwner.acquire(r,async r=>{const s=await t(i.instanceId,i.runtimeEpoch),l=r.client;u.setMessageClient(l,s.principalId);const c=new Map,m=new MessageProjectionInboundConsumer({client:l,bindings:()=>{const r=sharedRuntimeAuthority?.authorityLease;return r&&r.instanceId===s.principalId&&r.runtimeEpoch===s.runtimeEpoch?u.getPlatformAgentMappings().flatMap(({localAgentId:t,platformAgent:i})=>{if(!i.id||i.instanceId!==s.principalId)return[];let d=c.get(i.id);return d&&d.localAgentId===t||(d={localAgentId:t,inbound:createImInboundHandler({runtime:e.runtime,pluginConfig:e.pluginConfig,client:l,localServer:a(s,t,i.id),promptRunStore:p,sessionMutationCoordinator:g,logger:e.logger,writeObs:n,principalIdOverride:s.principalId,promptTimeoutMs:o.promptTimeoutMs,activeRunAuthority:u.getActiveRunAuthority()})},c.set(i.id,d)),[{principalId:s.principalId,targetAgentId:i.id,runtimeEpoch:r.runtimeEpoch,inbound:d.inbound}]}):[]},executionJournal:d,logger:e.logger});u.onAgentMappingUpdated(()=>{sharedActiveMessageConsumer===m&&m.refresh()});const y=sharedRuntimeAuthority?.subscribeAuthority(()=>{sharedActiveMessageConsumer===m&&m.refresh()})??(()=>{}),f=new MessageClientTerminalStateRecovery({logger:e.logger,isCurrent:()=>{if(sharedActiveMessageConsumer!==m)return!1;const e=sharedRuntimeAuthority?.authorityLease;return null!=e&&e.instanceId===s.principalId&&e.runtimeEpoch===s.runtimeEpoch&&e.leaseId===i.leaseId&&e.handlerIdentity===i.handlerIdentity},reconnect:async()=>{if("failed"===l.getSnapshot().connection){await l.connect();try{await u.rebindRuntimeDelivery()}catch(r){e.logger.warn?.(`[beeos-claw] runtime Service delivery rebind failed after MessageClient recovery: ${r instanceof Error?r.message:String(r)}`)}}}});let h=l.getSnapshot().connection;const w=l.subscribe(()=>{const r=l.getSnapshot();r.connection!==h&&(h=r.connection,"connected"===r.connection?(f.notifyConnected(),e.logger.info?.(`[beeos-claw] MessageClient connected (localAgentId=${u.getLocalAgentId()}, principalId=${s.principalId})`)):"failed"===r.connection?(e.logger.warn?.(`[beeos-claw] MessageClient failed principalId=${s.principalId} recoveryError=${r.recoveryError??"unknown"}`),f.notifyFailed()):"disconnected"!==r.connection&&"reconnecting"!==r.connection||e.logger.warn?.(`[beeos-claw] MessageClient state=${r.connection} principalId=${s.principalId}; SDK owns reconnect/recovery`))});sharedActiveMessageConsumer=m,m.start();try{await l.connect()}catch(e){throw sharedActiveMessageConsumer===m&&(sharedActiveMessageConsumer=void 0),f.dispose(),await m.stop(),y(),w(),await l.disconnect().catch(()=>{}),e}try{await u.rebindRuntimeDelivery()}catch(r){e.logger.warn?.(`[beeos-claw] runtime Service delivery rebind failed after MessageClient connect: ${r instanceof Error?r.message:String(r)}`)}return async()=>{sharedActiveMessageConsumer===m&&(sharedActiveMessageConsumer=void 0),f.dispose(),await m.stop(),y(),w(),await l.disconnect(),e.logger.info?.("[beeos-claw] MessageClient disconnected")}})},disconnect:async()=>{await sharedMessageClientOwner.release(r)}}}):void 0;e.registerService({id:"beeos-message-bus",async start(){i?i.start():e.logger.warn?.("[beeos-claw] Message bus requires runtime authority; waiting is unavailable without Agent Gateway runtime identity")},async stop(){await(i?.stop())}})}else m?e.logger.warn?.("[beeos-claw] Message bus enabled but Agent Gateway token provider is unavailable — disabled"):e.logger.info?.(`[beeos-claw] Message bus disabled (enabled=${m})`);if(!1!==f&&o.agentGatewayUrl){const r=e=>u.getGatewaySessionKeyForToolCall(e),t=e=>u.getMentionsForToolCall(e),n=(e,r)=>{u.broadcastNotification(e,r)},a=new TaskHandleRegistry;registerA2ATools(e,{agentGatewayUrl:o.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},a,r,t,n,()=>u?.getDefaultPlatformAgent()?.instanceId),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(o.canvasEnabled){sharedCanvasState||(sharedCanvasState=new CanvasStateManager);const r=sharedCanvasState;if(o.agentGatewayUrl&&agentKeys){let t;r.setLogger(e.logger);{const e=o.agentGatewayUrl,r=agentKeys;t=async()=>{const t=await agentFetch(`${e}/api/v1/canvas/token`,r,{method:"POST"});if(!t.ok){const e=await t.text().catch(()=>"");throw new Error(`Canvas token returned ${t.status}: ${e}`)}return t.json()}}const n=n=>{const a=new CanvasRelayClient({canvasRelayUrl:o.canvasRelayUrl,agentKeys:agentKeys,agentId:n.id,displayName:n.displayName,avatarUrl:n.avatarUrl,tokenProvider:t,onConnected:()=>u.flushPendingCanvasLinks(),onReconnect:()=>u.onRelayReconnected(),logger:e.logger});r.setRelayClient(a),r.setAgentId(n.id),u.wireRelayActionHandler(),a.connect(),e.logger.info?.(`[beeos-claw] Canvas Relay multiplexed client connected (agentId=${n.id}, name=${n.name??"none"})`)};e.registerService({id:"beeos-canvas-relay",async start(){try{await u.waitForAgentMapping();const r=u.getDefaultPlatformAgent();if(r?.id)return void n(r);e.logger.info?.("[beeos-claw] Canvas Relay deferred: waiting for late agent sync..."),u.onceAgentMappingReady(()=>{const r=u.getDefaultPlatformAgent();if(r?.id)try{n(r)}catch(r){e.logger.warn?.(`[beeos-claw] Deferred Canvas Relay start failed: ${r}`)}else e.logger.warn?.("[beeos-claw] Agent mapping ready but no platform agent; Canvas Relay stays disabled")})}catch(r){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${r}`)}},stop(){r.getRelayClient()?.disconnect()}}),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const t={stateManager:r,logger:e.logger,writeObs:e=>n(e),isCanvasActive:e=>!!e&&u.isCanvasActiveForGatewayKey(e),getActiveSessionId:e=>u.getSessionIdForToolCall(e),getPlatformAgentId:e=>u.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>u.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>u.waitForAgentMapping()},i=new FormSchemaRegistry,s=buildCronSchemas(a,{capMs:o.cronMaxDurationCapMs});i.register(s.create),i.register(s.update);const l=new ResourceDescriptorRegistry;l.register(CRON_RESOURCE);const c=e=>async(r,t,o)=>(u.preMapToolCallSession(r),e(r,t));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:c(createRenderUiHandler(t))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:c(createCloseUiHandler(t))}),u.setCanvasState(r,{schemaRegistry:i,descriptorRegistry:l});const d=u.getFormBuilder();d&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(i),parameters:RENDER_FORM_TOOL_SCHEMA,execute:c(createRenderFormHandler({registry:i,builder:d,logger:e.logger}))});const g=u.getResourceRenderer();if(g){generateActionTools(l,e,async(e,r)=>u.gatewayRequest(e,r),g,e.logger)}const p=`\n\nTool selection guidance:\n- browser tool: Use when the user wants to OPEN, VISIT, NAVIGATE to a webpage, take SCREENSHOTS of web pages, interact with elements (click, type), or visually inspect pages.\n- ${BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME}: Use to capture the agent desktop (XFCE/VNC DISPLAY). Prefer this over shell/python X11 dumps; never invent raw Xlib/PIL capture.\n- beeos_search: Use ONLY for text-based information retrieval (fact-finding, research queries). Never for page interaction or screenshots.\n- beeos_fetch: Use ONLY to extract article text from a known URL. Never for visual content or screenshots.\n- ${BEEOS_FILE_RESOURCE_ROUTING_RULE}\nWhen the user says "open", "visit", "go to", or mentions seeing/viewing a webpage, ALWAYS prefer the browser tool. When they ask for a desktop / screen / VNC capture of the agent workspace, use beeos_desktop_screenshot.`,m=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),y=256,f=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,r)=>{if(r?.sessionKey){if(f.size>=y&&!f.has(r.sessionKey)){const e=f.keys().next().value;void 0!==e&&f.delete(e)}f.set(r.sessionKey,r.channelId)}if(isThirdPartyChannel(a,{sessionKey:r?.sessionKey,channelId:r?.channelId}))return{appendSystemContext:'This conversation is in a third-party channel (Lark/Telegram/Discord/Slack); Canvas surfaces are not visible here. Do NOT use render_ui, close_ui, or render_form. Use built-in tools directly (e.g. the `cron` tool with `action:"add"` for scheduled tasks) and respond with plain text.'};if(!r?.sessionKey)return{appendSystemContext:p};return u.isCanvasActiveForGatewayKey(r.sessionKey)?{appendSystemContext:CANVAS_SYSTEM_PROMPT+p}:{appendSystemContext:"Canvas is closed by user. Do NOT use render_ui, close_ui, or render_form. Respond with plain text only."+p}}),e.logger.debug?.("[beeos-claw] Canvas system prompt hook registered: before_prompt_build")}catch{e.logger.debug?.("[beeos-claw] before_prompt_build hook not supported")}try{e.on("before_tool_call",(e,r)=>{if(r?.sessionKey&&u.setPendingToolCallSessionKey(r.sessionKey),!m.has(e.toolName))return;const t=r?.sessionKey?f.get(r.sessionKey):void 0;return isThirdPartyChannel(a,{sessionKey:r?.sessionKey,channelId:t})?{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."}:r?.sessionKey?u.isCanvasActiveForGatewayKey(r.sessionKey)?void 0:{block:!0,blockReason:"Canvas is closed by user. Respond with plain text instead."}:{block:!0,blockReason:"Canvas session unknown. Respond with plain text instead."}}),e.logger.debug?.("[beeos-claw] Canvas tool guard registered: before_tool_call")}catch{e.logger.debug?.("[beeos-claw] before_tool_call hook not supported")}}e.logger.info?.("[beeos-claw] Canvas tools registered (render_ui, close_ui, render_form + auto-action tools)")}else e.logger.info?.("[beeos-claw] Canvas disabled by config (canvasEnabled=false)");if(o.bridge.shell.enabled&&!0===o.legacyTerminalWsEnabled){const r=createTerminalWebSocketService({config:{port:o.terminalWsPort,token:agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):"",allowedOrigins:o.terminalWsAllowedOrigins},terminalMgr:sharedTerminalManager,logger:e.logger,writeObs:n});e.registerService({id:"beeos-terminal-ws",start(){r.start(),e.logger.info?.(`[beeos-claw] Terminal WS started on port ${o.terminalWsPort}`)},stop(){r.stop(),e.logger.info?.("[beeos-claw] Terminal WS stopped")}})}else o.bridge.shell.enabled?e.logger.info?.("[beeos-claw] Legacy static-token Terminal WS disabled (default)"):e.logger.info?.("[beeos-claw] Terminal/shell disabled by config (bridge.shell.enabled=false)");if(r.openclawConfig){const t=mirrorOpenClawConfig(r.openclawConfig);t.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${t.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${t.reason}`)}}const CAPABILITY_REGISTRATION_MODES=new Set(["full","discovery","tool-discovery"]),plugin={id:"beeos-claw",name:"beeos-claw",description:"BeeOS platform bridge plugin for OpenClaw. Provides file upload/download, ACP bridge, and observability.",version:PLUGIN_VERSION,register(e){if(!_initializing)if(!e.registrationMode||CAPABILITY_REGISTRATION_MODES.has(e.registrationMode)){_initializing=!0;try{if(!hasRuntimeReplyApi(e.runtime))throw e.logger.error?.("[beeos-claw] FATAL: OpenClaw runtime.channel.reply.dispatchReplyFromConfig not exposed. Upgrade OpenClaw or revert beeos-claw."),new Error("openclaw_runtime_reply_api_unavailable");const r=initializePlugin(e);if(!r)return;const t={current:null};installUnhandledRejectionObserver(e,r.writeObs,t),registerUploadTools(e,r,t),registerArouterMediaProviders(e),registerServices(e,r,t),_pluginRegisteredAt=Date.now(),e.logger.info?.("[beeos-claw] Plugin registered successfully")}finally{_initializing=!1}}else e.logger.debug?.(`[beeos-claw] Skipping registration (mode=${e.registrationMode})`)}};export default plugin;
1
+ import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{hasRuntimeReplyApi}from"./runtime/reply-router.js";import{PromptRunStore}from"./runtime/prompt-run-store.js";import{SessionWriteGate}from"./acp-gateway/session-write-gate.js";import{OpenClawSessionWriteQuiescence}from"./runtime/openclaw-session-write-quiescence.js";import{SessionMutationCoordinator}from"./runtime/session-mutation-coordinator.js";import{scheduleProcessRuntimeRestart}from"./runtime/runtime-restart-scheduler.js";import{createImInboundHandler}from"./im-inbound-handler.js";import{createTokenProvider}from"@beeos-ai/message-sdk/auth";import{BeeosClawMessageClientProcessOwner}from"./message-client-composition.js";import{MessageProjectionInboundConsumer}from"./message-projection-inbound-consumer.js";import{MessageClientTerminalStateRecovery}from"./runtime/message-client-terminal-state-recovery.js";import{RuntimeAuthorityMessageBusLifecycle}from"./runtime/runtime-authority-message-bus-lifecycle.js";import{FileInboundExecutionJournal}from"./inbound-execution-journal.js";import{completeA2ATask,registerA2ATools,TaskHandleRegistry}from"./a2a/tools.js";import{BEEOS_UPLOAD_TOOL_NAME,BEEOS_UPLOAD_TOOL_SCHEMA,BEEOS_UPLOAD_TOOL_DESCRIPTION,executeUpload}from"./upload/tool.js";import{BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME,BEEOS_DESKTOP_SCREENSHOT_TOOL_SCHEMA,BEEOS_DESKTOP_SCREENSHOT_TOOL_DESCRIPTION,executeDesktopScreenshot}from"./upload/desktop-screenshot-tool.js";import{BEEOS_LIST_FILES_TOOL_NAME,BEEOS_LIST_FILES_TOOL_SCHEMA,BEEOS_LIST_FILES_TOOL_DESCRIPTION,executeListFiles}from"./files/list-tool.js";import{BEEOS_FETCH_FILE_TOOL_NAME,BEEOS_FETCH_FILE_TOOL_SCHEMA,BEEOS_FETCH_FILE_TOOL_DESCRIPTION,executeFetchFile}from"./files/fetch-tool.js";import{writeUploadToolResult,cleanupUploadToolResultCache}from"./upload/result-cache.js";import{handleFileStage}from"./upload/file-stage.js";import{handleStatusRequest}from"./status.js";import{ACPServer}from"./acp-server.js";import{createObsEventWriter}from"./observability.js";import{resolvePluginVersion}from"./plugin-version.js";import{createTerminalWebSocketService}from"./terminal/websocket.js";import{TerminalManager}from"./terminal/session-manager.js";import{loadAgentKeyPair,agentBoundFetch,agentFetch}from"./agent-auth.js";import{readDiscoveryCache,writeDiscoveryCache}from"./discovery-cache.js";import{discoverBridgeFromGW}from"./bridge-discovery.js";import{setUploadDeps}from"./upload/upload-file.js";import{registerLocalFileExtractor}from"./upload/local-file-extractors.js";import{browserScreenshotExtractor}from"./browser/screenshot-extractor.js";import{BEEOS_FILE_RESOURCE_ROUTING_RULE}from"./files/beeos-file-uri.js";import{CanvasStateManager}from"./canvas/state.js";import{CanvasRelayClient}from"./canvas/relay-client.js";import{RENDER_UI_TOOL_NAME,RENDER_UI_TOOL_DESCRIPTION,RENDER_UI_TOOL_SCHEMA,CLOSE_UI_TOOL_NAME,CLOSE_UI_TOOL_DESCRIPTION,CLOSE_UI_TOOL_SCHEMA,createRenderUiHandler,createCloseUiHandler,CANVAS_SYSTEM_PROMPT}from"./canvas/tools.js";import{FormSchemaRegistry}from"./surface/form-schema-registry.js";import{buildCronSchemas}from"./surface/cron-schemas.js";import{RENDER_FORM_TOOL_NAME,RENDER_FORM_TOOL_SCHEMA,buildRenderFormDescription,createRenderFormHandler}from"./surface/render-form-tool.js";import{ResourceDescriptorRegistry}from"./surface/resource-descriptor-registry.js";import{CRON_RESOURCE}from"./surface/cron-resource.js";import{generateActionTools}from"./surface/action-tool-generator.js";import{buildThirdPartyChannelSet,isThirdPartyChannel}from"./channel-detection.js";import{registerArouterMediaProviders}from"./media/index.js";import{homedir}from"node:os";import{join}from"node:path";import{RuntimeProductionAuthority}from"./runtime/runtime-production-authority.js";import{FileRuntimeCommandJournal}from"./runtime/runtime-command-journal.js";import{FileRuntimePoisonDeliveryJournal}from"./runtime/runtime-poison-delivery-journal.js";import{InProcessMcpToolBridge}from"./acp-gateway/mcp-tool-bridge.js";import{LocalEncryptedCredentialReferenceVault}from"./acp-gateway/mcp-credential-reference-port.js";import{AgentGatewayGrantJwksReadinessAdapter,RuntimeServiceTransportReadinessAdapter,createRuntimeServiceProductionCandidate}from"./runtime/runtime-service-production-candidate.js";import{MessageServiceCronDeliveryHttpAuthority}from"./runtime/message-service-cron-delivery-http-authority.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null,sharedCanvasState=null,sharedAcpServer=null;const sharedSessionWriteGate=new SessionWriteGate,sharedPromptRunStore=new PromptRunStore;let sharedSessionMutationCoordinator,sharedRuntimeAuthority,sharedMcpToolBridge,sharedMcpCredentialVault;const sharedMessageClientOwner=new BeeosClawMessageClientProcessOwner;let sharedActiveMessageConsumer,sharedInboundExecutionJournal;const sharedTerminalManager=new TerminalManager;let _initializing=!1,_pluginRegisteredAt=0;const ED25519_PKCS8_PREFIX=Buffer.from("302e020100300506032b657004220420","hex");function agentKeyPairToIdentity(e){return{publicKeyBase64:()=>Buffer.from(e.publicKey).toString("base64"),sign:async r=>{const{createPrivateKey:t,sign:o}=await import("node:crypto"),n=t({key:32===e.privateKey.length?Buffer.concat([ED25519_PKCS8_PREFIX,Buffer.from(e.privateKey)]):Buffer.from(e.privateKey),format:"der",type:"pkcs8"});return new Uint8Array(o(null,Buffer.from(r),n))}}}let unhandledRejectionObserverInstalled=!1;function installUnhandledRejectionObserver(e,r,t){unhandledRejectionObserverInstalled||(unhandledRejectionObserverInstalled=!0,process.on("unhandledRejection",t=>{const o=t instanceof Error?t.message:String(t),n=t instanceof Error?t.stack:void 0;e.logger.error?.(`[beeos-claw] unhandledRejection: ${o}${n?`\n${n}`:""}`),r({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:o,error:{code:"unhandled_rejection",message:o,...n?{stack:n}:{}}})}),process.on("uncaughtException",t=>{const o=t instanceof Error?t.message:String(t);e.logger.error?.(`[beeos-claw] uncaughtException: ${t instanceof Error?t.stack??t.message:String(t)}`),r({component:"plugin",domain:"fatal",name:"uncaught_exception",severity:"error",summary:o,error:{code:"uncaught_exception",message:o}})}))}function initializePlugin(e){let r,t;try{r=e.runtime?.config?.loadConfig?.()}catch{}try{t=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:r})}catch(r){return e.logger.error?.(`[beeos-claw] Failed to resolve config: ${r instanceof Error?r.message:String(r)}`),null}const o=t.config,n=buildThirdPartyChannelSet(r);n.size>0&&e.logger.info?.(`[beeos-claw] Third-party channels detected: ${[...n].join(", ")}`);const a=o.log.enabled?createObsEventWriter({dir:o.log.dir,redaction:{gatewayToken:o.gateway.token},logger:e.logger}):()=>{};a({component:"plugin",domain:"lifecycle",name:"lifecycle.startup",severity:"info",payload:{pluginVersion:PLUGIN_VERSION,pid:process.pid,nodeVersion:process.version,cwd:process.cwd()}}),a({component:"plugin",domain:"config",name:"config.resolved",severity:"info",payload:{config:o,sources:t.sources,openclawConfigPath:t.openclawConfigPath}});for(const r of t.validation)a({component:"plugin",domain:"config",name:"config.validation",severity:r.severity,summary:r.message,error:{code:r.code,message:r.message,nextSteps:r.nextSteps}}),"error"===r.severity?e.logger.error?.(`[beeos-claw] ${r.message}`):e.logger.warn?.(`[beeos-claw] ${r.message}`);if(t.validation.some(e=>"error"===e.severity)&&e.logger.error?.("[beeos-claw] Plugin has configuration errors — some features may not work."),o.keyFile)try{agentKeys=loadAgentKeyPair(o.keyFile);const r=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${r})`)}catch(r){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${r}`),e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no key pair — signed requests disabled)`)}else e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (no keyFile configured)`);return agentKeys&&o.agentGatewayUrl&&(setUploadDeps({gatewayUrl:o.agentGatewayUrl,keys:agentKeys,logger:e.logger}),registerLocalFileExtractor(browserScreenshotExtractor),e.logger.info?.("[beeos-claw] Upload deps + browser screenshot extractor registered")),{cfg:o,writeObs:a,thirdPartyChannels:n,openclawConfig:r}}function registerUploadTools(e,r,t){const{cfg:o,writeObs:n}=r;cleanupUploadToolResultCache().catch(r=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(r)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(r,t,a){e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} params=${JSON.stringify(t)}`);const s=await executeUpload(t,o,agentKeys,a);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${r} ok=${s.ok}`),n({component:"upload-tool",domain:"tool",name:s.ok?"upload.success":"upload.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,fileCount:s.files?.length??0,error:s.error}}),(async t=>{try{await writeUploadToolResult(r,BEEOS_UPLOAD_TOOL_NAME,t),await cleanupUploadToolResultCache()}catch(t){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${r}: ${String(t)}`)}return t})(s)}}),e.registerTool({name:BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME,description:BEEOS_DESKTOP_SCREENSHOT_TOOL_DESCRIPTION,parameters:BEEOS_DESKTOP_SCREENSHOT_TOOL_SCHEMA,async execute(r,t,a){e.logger.debug?.(`[beeos_desktop_screenshot] toolCallId=${r} params=${JSON.stringify(t)}`);const s=await executeDesktopScreenshot(t,o,agentKeys,a);return e.logger.debug?.(`[beeos_desktop_screenshot] toolCallId=${r} ok=${s.ok}`),n({component:"desktop-screenshot-tool",domain:"tool",name:s.ok?"desktop_screenshot.success":"desktop_screenshot.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,path:s.path,fileCount:s.files?.length??0,error:s.error}}),(async t=>{try{await writeUploadToolResult(r,BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME,t),await cleanupUploadToolResultCache()}catch(t){e.logger.warn?.(`[beeos_desktop_screenshot] persist cache failed toolCallId=${r}: ${String(t)}`)}return t})(s)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,t){await handleFileStage(e,t,r.cfg)}}),e.registerHttpRoute({path:"/beeos/status",auth:"plugin",handler(e,r){handleStatusRequest(e,r,{publicKey:agentKeys?.publicKey??null,pluginVersion:PLUGIN_VERSION})}}),e.registerTool({name:BEEOS_LIST_FILES_TOOL_NAME,description:BEEOS_LIST_FILES_TOOL_DESCRIPTION,parameters:BEEOS_LIST_FILES_TOOL_SCHEMA,async execute(r,t,a){e.logger.debug?.(`[beeos_list_files] toolCallId=${r} params=${JSON.stringify(t)}`);const s=await executeListFiles(t,o,agentKeys,a);return n({component:"file-list-tool",domain:"tool",name:s.ok?"list_files.success":"list_files.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,fileCount:s.files?.length??0,error:s.error}}),s}}),e.registerTool({name:BEEOS_FETCH_FILE_TOOL_NAME,description:BEEOS_FETCH_FILE_TOOL_DESCRIPTION,parameters:BEEOS_FETCH_FILE_TOOL_SCHEMA,async execute(r,t,a){e.logger.debug?.(`[beeos_fetch_file] toolCallId=${r} params=${JSON.stringify(t)}`);const s=await executeFetchFile(t,o,agentKeys,{signal:a,resolvePath:e.resolvePath});return n({component:"file-fetch-tool",domain:"tool",name:s.ok?"fetch_file.success":"fetch_file.failure",severity:s.ok?"info":"warn",payload:{toolCallId:r,ok:s.ok,path:s.path,size:s.size,error:s.error}}),s}})}function registerServices(e,r,t){const{cfg:o,writeObs:n,thirdPartyChannels:a}=r,s=agentKeys?(e,r)=>agentFetch(e,agentKeys,r):void 0,i=agentKeys?(e,r)=>agentBoundFetch(e,agentKeys,r):void 0,l=agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):void 0,c=join(process.env.OPENCLAW_HOME||join(homedir(),".openclaw"),"beeos"),g=sharedInboundExecutionJournal??=new FileInboundExecutionJournal(join(c,"inbound-execution-journal.v1.jsonl"));sharedMcpToolBridge??=new InProcessMcpToolBridge,!sharedMcpCredentialVault&&agentKeys&&l&&(sharedMcpCredentialVault=new LocalEncryptedCredentialReferenceVault(join(c,"mcp-credential-vault.json"),l,agentKeys.privateKey)),sharedMcpToolBridge.registerTools(e),!sharedRuntimeAuthority&&agentKeys&&o.agentGatewayUrl&&s&&i&&(sharedRuntimeAuthority=new RuntimeProductionAuthority({stateDirectory:c,agentGatewayUrl:o.agentGatewayUrl,agentFetch:s,boundAgentFetch:i,keys:agentKeys,logger:e.logger})),sharedSessionMutationCoordinator??=new SessionMutationCoordinator(sharedSessionWriteGate,new OpenClawSessionWriteQuiescence(e.logger,{agentId:o.gateway.agentId||"main"}),e.logger,{onRecoveryRequired:r=>{scheduleProcessRuntimeRestart(r,e.logger)}});const d=sharedSessionMutationCoordinator;sharedAcpServer||(sharedAcpServer=new ACPServer(o,e.logger,d,n,s,l,a,sharedTerminalManager,sharedRuntimeAuthority,void 0,sharedMcpToolBridge,sharedMcpCredentialVault,i));const u=sharedAcpServer;t.current=u,u.nudgeMcpNativeProjection();const p=sharedPromptRunStore,{enabled:m,principalId:y,a2aEnabled:f}=o.messageBus;let h;o.agentGatewayUrl&&agentKeys&&(h=createTokenProvider({agentGatewayUrl:o.agentGatewayUrl,identity:agentKeyPairToIdentity(agentKeys)}));const w=process.env.MESSAGE_RUNTIME_DELIVERY_KEY?.trim(),E=y||process.env.BEEOS_PRINCIPAL_ID||"",_=h?sharedMessageClientOwner.configure({tokenProvider:h,identityId:E,...o.messageBus.serviceUrl?{serviceUrl:o.messageBus.serviceUrl}:{},logger:e.logger,...sharedRuntimeAuthority&&w?{runtimeDelivery:{scopedDeliveryKey:w,authority:{currentLease:()=>{const e=sharedRuntimeAuthority?.authorityLease,r=sharedRuntimeAuthority?.currentConsumerBinding();return e&&r?{...e,journalStoreId:r.journalStoreId,journalGeneration:r.journalGeneration}:null}}}}:{}}):void 0,v=sharedRuntimeAuthority,b=o.messageBus.serviceUrl;if(_&&v&&b&&w){const e=_.nodeTransport;u.setCronConversationDelivery({deliver:async r=>{const t=v.authorityLease;if(!t)throw new Error("current runtime authority lease unavailable for cron delivery");const o=new MessageServiceCronDeliveryHttpAuthority({messageServiceUrl:b,fetchAuthorized:async(r,o)=>await e.fetchRuntimeChannel(r,{runtimeLeaseCredential:t.runtimeLeaseCredential},o)});return await o.deliver({...r,runtimeLeaseCredential:t.runtimeLeaseCredential})}})}else u.setCronConversationDelivery(void 0);e.registerService({id:"beeos-acp",async start(){const r=Date.now(),t=_pluginRegisteredAt?r-_pluginRegisteredAt:-1;e.logger.info?.(`[beeos-claw] beeos-acp service start() begin, sincePluginRegister=${t}ms, bridge.url=${o.bridge.url||"(empty)"}`);const n=await createRuntimeServiceProductionCandidate({...o.agentGatewayUrl&&s?{authorizationAdapter:new AgentGatewayGrantJwksReadinessAdapter({agentGatewayUrl:o.agentGatewayUrl})}:{},..._&&o.agentGatewayUrl&&i&&w?{transportAdapter:new RuntimeServiceTransportReadinessAdapter({agentGatewayUrl:o.agentGatewayUrl,messageService:_.nodeTransport,messageClient:_.client,configuredServiceUrl:o.messageBus.serviceUrl,boundAgentFetch:i,scopedDeliveryKey:w})}:{},commandJournal:sharedRuntimeAuthority?.journal??new FileRuntimeCommandJournal(join(c,"runtime-command-journal.json")),poisonJournal:new FileRuntimePoisonDeliveryJournal(join(c,"runtime-poison-deliveries.json")),onConsumerDeliveryError:r=>{const t=r instanceof Error?`${r.name}: ${r.message}${"status"in r?` status=${r.status}`:""}${"code"in r?` code=${r.code}`:""}\n${r.stack??""}`:String(r);e.logger.error?.(`[runtime-service] delivery worker failed before acknowledgement; delivery retained for retry: ${t}`)},onConsumerLeaseStarvation:({starvedForMs:r,recovered:t})=>{const o=Math.round(r/1e3);t?e.logger.info?.(`[runtime-service] runtime authority lease restored after ${o}s without delivery reads`):e.logger.warn?.(`[runtime-service] no runtime authority lease for ${o}s; delivery reads suspended, queued runtime operations (model switch, session control) will not be consumed`)},onConsumerRequestFailure:r=>e.logger.error?.(`[runtime-service] request failed operationId=${r.operationId} method=${r.method}: ${r.error instanceof Error?r.error.stack??r.error.message:String(r.error)}`)});if(sharedRuntimeAuthority?.setRuntimeServiceMethods("ready"===n.status?n.serviceMethods:[]),sharedRuntimeAuthority?.setRuntimeResourceBindingContractRevision("ready"===n.status?n.resourceBindingContractRevision:void 0),u.setRuntimeRpcConsumerFactory("ready"===n.status?n.factory:void 0),"unavailable"===n.status?e.logger.warn?.("[runtime-service] capability fenced: "+n.blockers.map(e=>e.id).join(",")):n.capabilityBlockers.length&&e.logger.warn?.("[runtime-service] method capability fenced: "+n.capabilityBlockers.map(e=>e.method).join(",")),!o.bridge.url&&o.agentGatewayUrl&&s){const r=Date.now(),t=await discoverBridgeFromGW(o.agentGatewayUrl,s);if(e.logger.info?.(`[beeos-claw] bridge discovery took ${Date.now()-r}ms result=${t?.url??"null"}`),t)o.bridge.url=t.url,writeDiscoveryCache({bridge:{url:t.url,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${t.url}`);else{const r=readDiscoveryCache();r?.bridge?.url?(o.bridge.url=r.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${r.bridge.url}`)):e.logger.debug?.("[beeos-claw] Bridge URL not available (no GW, no cache) — bridge disabled")}}await u.start(o.bridge.url||void 0),e.logger.info?.(`[beeos-claw] ACP Server started (total service start: ${Date.now()-r}ms)`)},async stop(){await u.stop(),e.logger.info?.("[beeos-claw] ACP Server stopped")}});if(m&&void 0!==h&&h){const r=sharedMessageClientOwner.createRegistrationToken(),t=async(e,r)=>{const t=await h(e);if(!t.identityId||t.identityId!==e)throw new Error("Message SDK token identity did not match the current runtime authority");return{principalId:e,runtimeEpoch:r}},a=(e,r,t)=>({getLocalAgentId:()=>r,resolveLocalAgentId:e=>e===t?r:void 0,getDefaultPlatformAgent:()=>({instanceId:e.principalId}),ensureImInboundSession:(e,r,t,o)=>{u.ensureImInboundSession(e,r,t,o)},chatAbort:(e,r)=>u.chatAbort(e,r)}),s=sharedRuntimeAuthority?new RuntimeAuthorityMessageBusLifecycle({authority:sharedRuntimeAuthority,logger:e.logger,bus:{connect:async s=>{await sharedMessageClientOwner.acquire(r,async r=>{const i=await t(s.instanceId,s.runtimeEpoch),l=r.client;u.setMessageClient(l,i.principalId);const c=new Map,m=new MessageProjectionInboundConsumer({client:l,bindings:()=>{const r=sharedRuntimeAuthority?.authorityLease;return r&&r.instanceId===i.principalId&&r.runtimeEpoch===i.runtimeEpoch?u.getPlatformAgentMappings().flatMap(({localAgentId:t,platformAgent:s})=>{if(!s.id||s.instanceId!==i.principalId)return[];let g=c.get(s.id);return g&&g.localAgentId===t||(g={localAgentId:t,inbound:createImInboundHandler({runtime:e.runtime,pluginConfig:e.pluginConfig,client:l,localServer:a(i,t,s.id),promptRunStore:p,sessionMutationCoordinator:d,logger:e.logger,writeObs:n,principalIdOverride:i.principalId,promptTimeoutMs:o.promptTimeoutMs,activeRunAuthority:u.getActiveRunAuthority(),...o.agentGatewayUrl&&agentKeys?{completeA2ATask:(r,t,n)=>completeA2ATask({agentGatewayUrl:o.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},r,t,n)}:{}})},c.set(s.id,g)),[{principalId:i.principalId,targetAgentId:s.id,runtimeEpoch:r.runtimeEpoch,inbound:g.inbound}]}):[]},executionJournal:g,logger:e.logger});u.onAgentMappingUpdated(()=>{sharedActiveMessageConsumer===m&&m.refresh()});const y=sharedRuntimeAuthority?.subscribeAuthority(()=>{sharedActiveMessageConsumer===m&&m.refresh()})??(()=>{}),f=new MessageClientTerminalStateRecovery({logger:e.logger,isCurrent:()=>{if(sharedActiveMessageConsumer!==m)return!1;const e=sharedRuntimeAuthority?.authorityLease;return null!=e&&e.instanceId===i.principalId&&e.runtimeEpoch===i.runtimeEpoch&&e.leaseId===s.leaseId&&e.handlerIdentity===s.handlerIdentity},reconnect:async()=>{if("failed"===l.getSnapshot().connection){await l.connect();try{await u.rebindRuntimeDelivery()}catch(r){e.logger.warn?.(`[beeos-claw] runtime Service delivery rebind failed after MessageClient recovery: ${r instanceof Error?r.message:String(r)}`)}}}});let h=l.getSnapshot().connection;const w=l.subscribe(()=>{const r=l.getSnapshot();r.connection!==h&&(h=r.connection,"connected"===r.connection?(f.notifyConnected(),e.logger.info?.(`[beeos-claw] MessageClient connected (localAgentId=${u.getLocalAgentId()}, principalId=${i.principalId})`)):"failed"===r.connection?(e.logger.warn?.(`[beeos-claw] MessageClient failed principalId=${i.principalId} recoveryError=${r.recoveryError??"unknown"}`),f.notifyFailed()):"disconnected"!==r.connection&&"reconnecting"!==r.connection||e.logger.warn?.(`[beeos-claw] MessageClient state=${r.connection} principalId=${i.principalId}; SDK owns reconnect/recovery`))});sharedActiveMessageConsumer=m,m.start();try{await l.connect()}catch(e){throw sharedActiveMessageConsumer===m&&(sharedActiveMessageConsumer=void 0),f.dispose(),await m.stop(),y(),w(),await l.disconnect().catch(()=>{}),e}try{await u.rebindRuntimeDelivery()}catch(r){e.logger.warn?.(`[beeos-claw] runtime Service delivery rebind failed after MessageClient connect: ${r instanceof Error?r.message:String(r)}`)}return async()=>{sharedActiveMessageConsumer===m&&(sharedActiveMessageConsumer=void 0),f.dispose(),await m.stop(),y(),w(),await l.disconnect(),e.logger.info?.("[beeos-claw] MessageClient disconnected")}})},disconnect:async()=>{await sharedMessageClientOwner.release(r)}}}):void 0;e.registerService({id:"beeos-message-bus",async start(){s?s.start():e.logger.warn?.("[beeos-claw] Message bus requires runtime authority; waiting is unavailable without Agent Gateway runtime identity")},async stop(){await(s?.stop())}})}else m?e.logger.warn?.("[beeos-claw] Message bus enabled but Agent Gateway token provider is unavailable — disabled"):e.logger.info?.(`[beeos-claw] Message bus disabled (enabled=${m})`);if(!1!==f&&o.agentGatewayUrl){const r=e=>u.getGatewaySessionKeyForToolCall(e),t=e=>u.getMentionsForToolCall(e),n=(e,r)=>{u.broadcastNotification(e,r)},a=new TaskHandleRegistry;registerA2ATools(e,{agentGatewayUrl:o.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},a,r,t,n,()=>u?.getDefaultPlatformAgent()?.instanceId),e.logger.info?.("[beeos-claw] A2A tools registered (beeos_discover_agents, beeos_call_agent) via REST")}if(o.canvasEnabled){sharedCanvasState||(sharedCanvasState=new CanvasStateManager);const r=sharedCanvasState;if(o.agentGatewayUrl&&agentKeys){let t;r.setLogger(e.logger);{const e=o.agentGatewayUrl,r=agentKeys;t=async()=>{const t=await agentFetch(`${e}/api/v1/canvas/token`,r,{method:"POST"});if(!t.ok){const e=await t.text().catch(()=>"");throw new Error(`Canvas token returned ${t.status}: ${e}`)}return t.json()}}const n=n=>{const a=new CanvasRelayClient({canvasRelayUrl:o.canvasRelayUrl,agentKeys:agentKeys,agentId:n.id,displayName:n.displayName,avatarUrl:n.avatarUrl,tokenProvider:t,onConnected:()=>u.flushPendingCanvasLinks(),onReconnect:()=>u.onRelayReconnected(),logger:e.logger});r.setRelayClient(a),r.setAgentId(n.id),u.wireRelayActionHandler(),a.connect(),e.logger.info?.(`[beeos-claw] Canvas Relay multiplexed client connected (agentId=${n.id}, name=${n.name??"none"})`)};e.registerService({id:"beeos-canvas-relay",async start(){try{await u.waitForAgentMapping();const r=u.getDefaultPlatformAgent();if(r?.id)return void n(r);e.logger.info?.("[beeos-claw] Canvas Relay deferred: waiting for late agent sync..."),u.onceAgentMappingReady(()=>{const r=u.getDefaultPlatformAgent();if(r?.id)try{n(r)}catch(r){e.logger.warn?.(`[beeos-claw] Deferred Canvas Relay start failed: ${r}`)}else e.logger.warn?.("[beeos-claw] Agent mapping ready but no platform agent; Canvas Relay stays disabled")})}catch(r){e.logger.warn?.(`[beeos-claw] Failed to start Canvas Relay client: ${r}`)}},stop(){r.getRelayClient()?.disconnect()}}),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const t={stateManager:r,logger:e.logger,writeObs:e=>n(e),isCanvasActive:e=>!!e&&u.isCanvasActiveForGatewayKey(e),getActiveSessionId:e=>u.getSessionIdForToolCall(e),getPlatformAgentId:e=>u.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>u.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>u.waitForAgentMapping()},s=new FormSchemaRegistry,i=buildCronSchemas(a,{capMs:o.cronMaxDurationCapMs});s.register(i.create),s.register(i.update);const l=new ResourceDescriptorRegistry;l.register(CRON_RESOURCE);const c=e=>async(r,t,o)=>(u.preMapToolCallSession(r),e(r,t));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:c(createRenderUiHandler(t))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:c(createCloseUiHandler(t))}),u.setCanvasState(r,{schemaRegistry:s,descriptorRegistry:l});const g=u.getFormBuilder();g&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(s),parameters:RENDER_FORM_TOOL_SCHEMA,execute:c(createRenderFormHandler({registry:s,builder:g,logger:e.logger}))});const d=u.getResourceRenderer();if(d){generateActionTools(l,e,async(e,r)=>u.gatewayRequest(e,r),d,e.logger)}const p=`\n\nTool selection guidance:\n- browser tool: Use when the user wants to OPEN, VISIT, NAVIGATE to a webpage, take SCREENSHOTS of web pages, interact with elements (click, type), or visually inspect pages.\n- ${BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME}: Use to capture the agent desktop (XFCE/VNC DISPLAY). Prefer this over shell/python X11 dumps; never invent raw Xlib/PIL capture.\n- beeos_search: Use ONLY for text-based information retrieval (fact-finding, research queries). Never for page interaction or screenshots.\n- beeos_fetch: Use ONLY to extract article text from a known URL. Never for visual content or screenshots.\n- ${BEEOS_FILE_RESOURCE_ROUTING_RULE}\nWhen the user says "open", "visit", "go to", or mentions seeing/viewing a webpage, ALWAYS prefer the browser tool. When they ask for a desktop / screen / VNC capture of the agent workspace, use beeos_desktop_screenshot.`,m=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),y=256,f=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,r)=>{if(r?.sessionKey){if(f.size>=y&&!f.has(r.sessionKey)){const e=f.keys().next().value;void 0!==e&&f.delete(e)}f.set(r.sessionKey,r.channelId)}if(isThirdPartyChannel(a,{sessionKey:r?.sessionKey,channelId:r?.channelId}))return{appendSystemContext:'This conversation is in a third-party channel (Lark/Telegram/Discord/Slack); Canvas surfaces are not visible here. Do NOT use render_ui, close_ui, or render_form. Use built-in tools directly (e.g. the `cron` tool with `action:"add"` for scheduled tasks) and respond with plain text.'};if(!r?.sessionKey)return{appendSystemContext:p};return u.isCanvasActiveForGatewayKey(r.sessionKey)?{appendSystemContext:CANVAS_SYSTEM_PROMPT+p}:{appendSystemContext:"Canvas is closed by user. Do NOT use render_ui, close_ui, or render_form. Respond with plain text only."+p}}),e.logger.debug?.("[beeos-claw] Canvas system prompt hook registered: before_prompt_build")}catch{e.logger.debug?.("[beeos-claw] before_prompt_build hook not supported")}try{e.on("before_tool_call",(e,r)=>{if(r?.sessionKey&&u.setPendingToolCallSessionKey(r.sessionKey),!m.has(e.toolName))return;const t=r?.sessionKey?f.get(r.sessionKey):void 0;return isThirdPartyChannel(a,{sessionKey:r?.sessionKey,channelId:t})?{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."}:r?.sessionKey?u.isCanvasActiveForGatewayKey(r.sessionKey)?void 0:{block:!0,blockReason:"Canvas is closed by user. Respond with plain text instead."}:{block:!0,blockReason:"Canvas session unknown. Respond with plain text instead."}}),e.logger.debug?.("[beeos-claw] Canvas tool guard registered: before_tool_call")}catch{e.logger.debug?.("[beeos-claw] before_tool_call hook not supported")}}e.logger.info?.("[beeos-claw] Canvas tools registered (render_ui, close_ui, render_form + auto-action tools)")}else e.logger.info?.("[beeos-claw] Canvas disabled by config (canvasEnabled=false)");if(o.bridge.shell.enabled&&!0===o.legacyTerminalWsEnabled){const r=createTerminalWebSocketService({config:{port:o.terminalWsPort,token:agentKeys?Buffer.from(agentKeys.publicKey).toString("base64"):"",allowedOrigins:o.terminalWsAllowedOrigins},terminalMgr:sharedTerminalManager,logger:e.logger,writeObs:n});e.registerService({id:"beeos-terminal-ws",start(){r.start(),e.logger.info?.(`[beeos-claw] Terminal WS started on port ${o.terminalWsPort}`)},stop(){r.stop(),e.logger.info?.("[beeos-claw] Terminal WS stopped")}})}else o.bridge.shell.enabled?e.logger.info?.("[beeos-claw] Legacy static-token Terminal WS disabled (default)"):e.logger.info?.("[beeos-claw] Terminal/shell disabled by config (bridge.shell.enabled=false)");if(r.openclawConfig){const t=mirrorOpenClawConfig(r.openclawConfig);t.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${t.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${t.reason}`)}}const CAPABILITY_REGISTRATION_MODES=new Set(["full","discovery","tool-discovery"]),plugin={id:"beeos-claw",name:"beeos-claw",description:"BeeOS platform bridge plugin for OpenClaw. Provides file upload/download, ACP bridge, and observability.",version:PLUGIN_VERSION,register(e){if(!_initializing)if(!e.registrationMode||CAPABILITY_REGISTRATION_MODES.has(e.registrationMode)){_initializing=!0;try{if(!hasRuntimeReplyApi(e.runtime))throw e.logger.error?.("[beeos-claw] FATAL: OpenClaw runtime.channel.reply.dispatchReplyFromConfig not exposed. Upgrade OpenClaw or revert beeos-claw."),new Error("openclaw_runtime_reply_api_unavailable");const r=initializePlugin(e);if(!r)return;const t={current:null};installUnhandledRejectionObserver(e,r.writeObs,t),registerUploadTools(e,r,t),registerArouterMediaProviders(e),registerServices(e,r,t),_pluginRegisteredAt=Date.now(),e.logger.info?.("[beeos-claw] Plugin registered successfully")}finally{_initializing=!1}}else e.logger.debug?.(`[beeos-claw] Skipping registration (mode=${e.registrationMode})`)}};export default plugin;
@@ -1 +1 @@
1
- import{toMessagePart}from"../message-client-types.js";import{resolveToolArtifactLinks}from"../upload/tool-artifact-enrichment.js";import{resolveRuntimeToolArguments,resolveRuntimeToolCallId,resolveRuntimeToolName}from"./tool-envelope-metadata.js";const isNonEmptyString=e=>"string"==typeof e&&e.length>0,isPlainRecord=e=>"object"==typeof e&&null!==e&&!Array.isArray(e);function pickRawResultFromPayload(e){if(void 0!==e.rawResult)return e.rawResult;if(void 0!==e.result)return e.result;if(void 0!==e.details)return e.details;if(void 0!==e.toolResult)return e.toolResult;if(isPlainRecord(e.channelData)){const t=e.channelData;if(void 0!==t.rawResult)return t.rawResult;if(void 0!==t.result)return t.result;if(void 0!==t.details)return t.details}}const extractBodyUpdate=(e,t)=>{if(!isNonEmptyString(t))return{kind:"noop"};if(t===e.text)return{kind:"noop"};if(t.startsWith(e.text)){const r=t.slice(e.text.length);return e.text=t,{kind:"append",text:r}}return e.text.startsWith(t)?{kind:"noop"}:(e.text=t,{kind:"replace",text:t})};export function createImReplySink(e){const t={text:""};let r,n="",o="";const s=new Map,i=new Map,a=[],l=[];let d=0,u=0;const m=new Set,g=(t,r,n)=>{const o=u;return e.stream.appendToolUse(t,r,toMessagePart(n),"streaming"),u+=1,o},c=()=>(d+=1,`tool_${e.requestId}_${d}`),p=e=>{const t=a.indexOf(e);t>=0&&a.splice(t,1)},f=e=>{const t=e?a.filter(t=>s.get(t)?.name===e):a;return 1===t.length?t[0]:void 0},y=e=>{const t=l.map((e,t)=>({hint:e,index:t})).filter(({hint:t})=>!e||t.name===e);if(1!==t.length)return;const[{hint:r,index:n}]=t;return l.splice(n,1),r},I=(e,t)=>{i.set(e,t)},h=e=>i.get(e)??e,v=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),$=(t,r,n,o)=>{const i=s.get(t);if(i){const s="tool"===i.name&&"tool"!==r?r:i.name,l=!v(i.arguments)&&v(n)?n:i.arguments;return s===i.name&&l===i.arguments||(e.stream.replacePart(i.partIndex,toMessagePart({type:"tool_use",id:i.id,name:s,arguments:l,state:i.state})),i.name=s,i.arguments=l),o&&!a.includes(t)&&"streaming"===i.state&&a.push(t),i}const l={id:t,name:r,arguments:n,partIndex:g(t,r,n),state:"streaming",itemEventObserved:!1,toolStartObserved:!1};return s.set(t,l),o&&a.push(t),l},k=t=>{"done"!==t.state&&(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done"})),t.state="done")},x=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(x).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${x(t)}`).join(",")}}`},b=(t,r,n)=>{const o=`${n?"1":"0"}:${x(r)}`;if(t.progressSignature===o)return;const s=toMessagePart({type:"custom",kind:"tool_progress",data:{tool_use_id:t.id,content:r,...n?{is_error:!0}:{}}});if(void 0===t.progressPartIndex){const r=u;return e.stream.addPart(s),t.progressPartIndex=r,t.progressSignature=o,void(u+=1)}e.stream.replacePart(t.progressPartIndex,s),t.progressSignature=o},R=e=>[e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e)),S=(e,t=[])=>{const r=[...new Set([...R(e),...t])];return isNonEmptyString(e.text)&&r.length>0?{text:e.text,media_urls:r}:isNonEmptyString(e.text)?e.text:1===r.length?r[0]:r.length>1?{media_urls:r}:!0===e.isError?"Tool execution failed.":""},_=(t,r)=>{if(t.durableUploadFilesEmitted)return;e.logger.info?.(`[im-sink] schedule tool artifact enrichment requestId=${e.requestId} toolUseId=${t.id} name=${t.name}`);const n=pickRawResultFromPayload(r),o=(async()=>{let o=[];try{o=await resolveToolArtifactLinks({toolCallId:t.id,toolName:t.name,rawResult:n,rawText:"string"==typeof r.text?r.text:void 0,payloadMediaUrls:R(r)})}catch(r){const n=r instanceof Error?r.message:String(r);return void e.logger.warn?.(`[im-sink] resolveToolArtifactLinks failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)}0!==o.length?(e.logger.info?.(`[im-sink] tool artifact enrichment resolved ${o.length} link(s) requestId=${e.requestId} toolUseId=${t.id}`),await new Promise(n=>{e.callbackQueue.enqueueSync(()=>{try{if(e.abortSignal.aborted||e.stream.isTerminated)return;const n=o.map(e=>e.uri);b(t,S(r,n),!0===r.isError),((t,r,n)=>{if(0!==r.length&&!t.durableUploadFilesEmitted){t.durableUploadFilesEmitted=!0,e.stream.appendToolResult(t.id,r.map(e=>({type:"resource_link",uri:e.uri,name:e.name,...e.mimeType?{mimeType:e.mimeType}:{}})),n),u+=1;for(const t of r)e.stream.addPart(toMessagePart({type:"resource_link",uri:t.uri,name:t.name,...t.mimeType?{mimeType:t.mimeType}:{}})),u+=1}})(t,o,!0===r.isError)}catch(r){const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool artifact enrichment apply failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)}finally{n()}})})):e.logger.debug?.(`[im-sink] tool artifact enrichment found no links requestId=${e.requestId} toolUseId=${t.id} name=${t.name}`)})().catch(r=>{const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool artifact enrichment failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)});m.add(o),o.finally(()=>{m.delete(o)})},T=(e,t,r)=>{const n=i.get(e);if(n)return $(n,t??"tool",r??{},!1);if(s.has(e))return $(e,t??"tool",r??{},!1);const o=f(t);return o?(I(e,o),$(o,t??"tool",r??{},!1)):$(e,t??"tool",r??{},!1)},q=(e,t)=>{const r=i.get(e);if(r)return $(r,t,{},!1);if(s.has(e))return $(e,t,{},!1);const n=(e=>{const t=a.filter(t=>{const r=s.get(t);return!(void 0===r||e&&r.name!==e||r.itemEventObserved)});return 1===t.length?t[0]:void 0})(t);if(n)return I(e,n),$(n,t,{},!1);const o=y(t);return $(e,t,o?.arguments??{},!1)},w={tool:0,block:0,final:0};let E,P=!1;const A=(t,r)=>{e.callbackQueue.enqueueSync(()=>{if((t=>e.abortSignal.aborted?(e.logger.debug?.(`[im-sink] ${t} ignored after abort requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):e.stream.isTerminated?(e.logger.debug?.(`[im-sink] ${t} ignored after stream_terminated requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):!P||(e.logger.debug?.(`[im-sink] ${t} ignored after mark_complete requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1))(t))try{r()}catch(r){const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] ${t} mapping failed requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId} error=${n}`)}})};return{dispatcher:{sendToolResult:t=>(A("tool_progress",()=>{const r=resolveRuntimeToolCallId(t),n=resolveRuntimeToolName(t),o=r?void 0:n?f(n):f(),i=r||o?void 0:y(n);if(!r&&!o&&!i)return void e.logger.debug?.(`[im-sink] ambiguous id-less tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${n??"unknown"} pending=${a.length}`);const l=r?T(r,n,resolveRuntimeToolArguments(t)):$(o??c(),n??s.get(o??"")?.name??i.name,v(resolveRuntimeToolArguments(t))?resolveRuntimeToolArguments(t):i?.arguments??{},!1);w.tool+=1,b(l,S(t),!0===t.isError),k(l),p(l.id),_(l,t)}),!0),sendBlockReply:r=>(A("block_reply",()=>{w.block+=1,isNonEmptyString(r.text)&&(!0!==r.isError?(E=void 0,e.stream.setBody(r.text),t.text=r.text):E=r.text)}),!0),sendFinalReply:r=>(A("final_reply",()=>{w.final+=1,isNonEmptyString(r.text)&&(!0!==r.isError?(E=void 0,e.stream.setBody(r.text),t.text=r.text):E=r.text)}),!0),waitForIdle:async()=>{for(let t=0;t<16;t++){if(await e.callbackQueue.waitForIdle(),0===m.size)return;await Promise.allSettled([...m])}await e.callbackQueue.waitForIdle()},getQueuedCounts:()=>({...w}),getAccumulatedBody:()=>t.text,getTerminalErrorText:()=>E,markComplete:t=>{if(1===l.length){const e=l.shift();$(c(),e.name,e.arguments,!1)}for(const r of s.values())try{"success"===t&&k(r),p(r.id)}catch(t){const n=t instanceof Error?t.message:String(t);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${r.id} error=${n}`)}l.length=0,P=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,onAgentRunStart:t=>{e.onAgentRunStart?.(t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${t} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:r=>{A("partial",()=>{const n=extractBodyUpdate(t,r.text);switch(n.kind){case"append":e.stream.appendBody(n.text);break;case"replace":e.stream.setBody(n.text)}})},onReasoningStream:t=>{A("reasoning",()=>{if(!isNonEmptyString(t.text))return;if(!r&&(r=e.openReasoningStream?.(),!r))return;const s=t.text;""===o&&""!==n&&(r.appendBody("\n"),n+="\n"),s!==o&&(s.startsWith(o)?(r.appendBody(s.slice(o.length)),o=s):o.startsWith(s)||(r.setBody(n+s),o=s))})},onReasoningEnd:()=>{A("reasoning_end",()=>{var t;""!==o&&(t=o,e.stream.appendThinking(t,"done"),u+=1,n+=o,o="")})},onAssistantMessageStart:()=>{e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)},onToolStart:t=>{A("tool_start",()=>{const r="string"==typeof t.phase?t.phase:"start",n=resolveRuntimeToolName(t)??"tool",o=resolveRuntimeToolCallId(t);if("update"===r){if(o){const e=h(o);s.has(e)&&$(e,n,resolveRuntimeToolArguments(t),!0)}return}const i=resolveRuntimeToolArguments(t);if(o){const e=T(o,n,i);return e.toolStartObserved=!0,void(a.includes(e.id)||"streaming"!==e.state||a.push(e.id))}const d=(u=n,a.map(e=>s.get(e)).filter(e=>void 0!==e&&(!u||e.name===u)&&e.itemEventObserved));var u;if(1===d.length){return void($(d[0].id,n,i,!0).toolStartObserved=!0)}d.length>1?e.logger.debug?.(`[im-sink] ambiguous id-less tool start ignored requestId=${e.requestId} channel=${e.channelId} tool=${n} candidates=${d.length}`):l.push({name:n,arguments:i})})},onItemEvent:e=>{A("item_event",()=>{const t="string"==typeof e.kind?e.kind.toLowerCase():"";if(t&&"tool"!==t)return;const r="string"==typeof e.phase?e.phase.toLowerCase():"update",n=resolveRuntimeToolCallId(e)??("string"==typeof e.itemId&&e.itemId.trim()?e.itemId.trim():void 0),o=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);if(!n||!o)return;if("update"===r){const e=h(n),t=s.get(e);return void(t&&($(t.id,o,{},!0).itemEventObserved=!0))}const i=q(n,o);i.itemEventObserved=!0,a.includes(i.id)||"streaming"!==i.state||a.push(i.id)})}}}}
1
+ import{toMessagePart}from"../message-client-types.js";import{resolveToolArtifactLinks}from"../upload/tool-artifact-enrichment.js";import{resolveRuntimeToolArguments,resolveRuntimeToolCallId,resolveRuntimeToolName}from"./tool-envelope-metadata.js";const isNonEmptyString=e=>"string"==typeof e&&e.length>0,isPlainRecord=e=>"object"==typeof e&&null!==e&&!Array.isArray(e);function pickRawResultFromPayload(e){if(void 0!==e.rawResult)return e.rawResult;if(void 0!==e.result)return e.result;if(void 0!==e.details)return e.details;if(void 0!==e.toolResult)return e.toolResult;if(isPlainRecord(e.channelData)){const t=e.channelData;if(void 0!==t.rawResult)return t.rawResult;if(void 0!==t.result)return t.result;if(void 0!==t.details)return t.details}}const extractBodyUpdate=(e,t)=>{if(!isNonEmptyString(t))return{kind:"noop"};if(t===e.text)return{kind:"noop"};if(t.startsWith(e.text)){const r=t.slice(e.text.length);return e.text=t,{kind:"append",text:r}}return e.text.startsWith(t)?{kind:"noop"}:(e.text=t,{kind:"replace",text:t})};export function createImReplySink(e){const t={text:""};let r,n="",o="";const s=new Map,i=new Map,a=[],l=[];let d=0,u=0,m="";const g=new Set,c=(t,r,n)=>{const o=u;return e.stream.addPart(toMessagePart({type:"tool_use",id:t,name:r,arguments:n,state:"streaming",_seq:o+1})),u+=1,o},p=()=>{const r=t.text;if(!r||r===m)return;const n=r.startsWith(m)?r.slice(m.length):r;if(!n)return void(m=r);const o=u;e.stream.addPart(toMessagePart({type:"text",text:n,state:"done",_seq:o+1})),u+=1,m=r},f=()=>(d+=1,`tool_${e.requestId}_${d}`),y=e=>{const t=a.indexOf(e);t>=0&&a.splice(t,1)},I=e=>{const t=e?a.filter(t=>s.get(t)?.name===e):a;return 1===t.length?t[0]:void 0},h=e=>{const t=l.map((e,t)=>({hint:e,index:t})).filter(({hint:t})=>!e||t.name===e);if(1!==t.length)return;const[{hint:r,index:n}]=t;return l.splice(n,1),r},v=(e,t)=>{i.set(e,t)},$=e=>i.get(e)??e,x=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),k=(t,r,n,o)=>{const i=s.get(t);if(i){const s="tool"===i.name&&"tool"!==r?r:i.name,l=!x(i.arguments)&&x(n)?n:i.arguments;return s===i.name&&l===i.arguments||(e.stream.replacePart(i.partIndex,toMessagePart({type:"tool_use",id:i.id,name:s,arguments:l,state:i.state,_seq:i.partIndex+1})),i.name=s,i.arguments=l),o&&!a.includes(t)&&"streaming"===i.state&&a.push(t),i}p();const l={id:t,name:r,arguments:n,partIndex:c(t,r,n),state:"streaming",itemEventObserved:!1,toolStartObserved:!1};return s.set(t,l),o&&a.push(t),l},b=t=>{"done"!==t.state&&(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done",_seq:t.partIndex+1})),t.state="done")},_=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(_).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${_(t)}`).join(",")}}`},R=(t,r,n)=>{const o=`${n?"1":"0"}:${_(r)}`;if(t.progressSignature===o)return;const s=toMessagePart({type:"custom",kind:"tool_progress",data:{tool_use_id:t.id,content:r,...n?{is_error:!0}:{}}});if(void 0===t.progressPartIndex){const r=u;return e.stream.addPart(s),t.progressPartIndex=r,t.progressSignature=o,void(u+=1)}e.stream.replacePart(t.progressPartIndex,s),t.progressSignature=o},S=e=>[e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e)),q=(e,t=[])=>{const r=[...new Set([...S(e),...t])];return isNonEmptyString(e.text)&&r.length>0?{text:e.text,media_urls:r}:isNonEmptyString(e.text)?e.text:1===r.length?r[0]:r.length>1?{media_urls:r}:!0===e.isError?"Tool execution failed.":""},T=(t,r)=>{if(t.durableUploadFilesEmitted)return;e.logger.info?.(`[im-sink] schedule tool artifact enrichment requestId=${e.requestId} toolUseId=${t.id} name=${t.name}`);const n=pickRawResultFromPayload(r),o=(async()=>{let o=[];try{o=await resolveToolArtifactLinks({toolCallId:t.id,toolName:t.name,rawResult:n,rawText:"string"==typeof r.text?r.text:void 0,payloadMediaUrls:S(r)})}catch(r){const n=r instanceof Error?r.message:String(r);return void e.logger.warn?.(`[im-sink] resolveToolArtifactLinks failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)}0!==o.length?(e.logger.info?.(`[im-sink] tool artifact enrichment resolved ${o.length} link(s) requestId=${e.requestId} toolUseId=${t.id}`),await new Promise(n=>{e.callbackQueue.enqueueSync(()=>{try{if(e.abortSignal.aborted||e.stream.isTerminated)return;const n=o.map(e=>e.uri);R(t,q(r,n),!0===r.isError),((t,r,n)=>{if(0!==r.length&&!t.durableUploadFilesEmitted){t.durableUploadFilesEmitted=!0,e.stream.appendToolResult(t.id,r.map(e=>({type:"resource_link",uri:e.uri,name:e.name,...e.mimeType?{mimeType:e.mimeType}:{}})),n),u+=1;for(const t of r)e.stream.addPart(toMessagePart({type:"resource_link",uri:t.uri,name:t.name,...t.mimeType?{mimeType:t.mimeType}:{}})),u+=1}})(t,o,!0===r.isError)}catch(r){const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool artifact enrichment apply failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)}finally{n()}})})):e.logger.debug?.(`[im-sink] tool artifact enrichment found no links requestId=${e.requestId} toolUseId=${t.id} name=${t.name}`)})().catch(r=>{const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool artifact enrichment failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)});g.add(o),o.finally(()=>{g.delete(o)})},w=(e,t,r)=>{const n=i.get(e);if(n)return k(n,t??"tool",r??{},!1);if(s.has(e))return k(e,t??"tool",r??{},!1);const o=I(t);return o?(v(e,o),k(o,t??"tool",r??{},!1)):k(e,t??"tool",r??{},!1)},E=(e,t)=>{const r=i.get(e);if(r)return k(r,t,{},!1);if(s.has(e))return k(e,t,{},!1);const n=(e=>{const t=a.filter(t=>{const r=s.get(t);return!(void 0===r||e&&r.name!==e||r.itemEventObserved)});return 1===t.length?t[0]:void 0})(t);if(n)return v(e,n),k(n,t,{},!1);const o=h(t);return k(e,t,o?.arguments??{},!1)},P={tool:0,block:0,final:0};let A,M=!1;const N=(t,r)=>{e.callbackQueue.enqueueSync(()=>{if((t=>e.abortSignal.aborted?(e.logger.debug?.(`[im-sink] ${t} ignored after abort requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):e.stream.isTerminated?(e.logger.debug?.(`[im-sink] ${t} ignored after stream_terminated requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1):!M||(e.logger.debug?.(`[im-sink] ${t} ignored after mark_complete requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`),!1))(t))try{r()}catch(r){const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] ${t} mapping failed requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId} error=${n}`)}})};return{dispatcher:{sendToolResult:t=>(N("tool_progress",()=>{const r=resolveRuntimeToolCallId(t),n=resolveRuntimeToolName(t),o=r?void 0:n?I(n):I(),i=r||o?void 0:h(n);if(!r&&!o&&!i)return void e.logger.debug?.(`[im-sink] ambiguous id-less tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${n??"unknown"} pending=${a.length}`);const l=r?w(r,n,resolveRuntimeToolArguments(t)):k(o??f(),n??s.get(o??"")?.name??i.name,x(resolveRuntimeToolArguments(t))?resolveRuntimeToolArguments(t):i?.arguments??{},!1);P.tool+=1,R(l,q(t),!0===t.isError),b(l),y(l.id),T(l,t)}),!0),sendBlockReply:r=>(N("block_reply",()=>{P.block+=1,isNonEmptyString(r.text)&&(!0!==r.isError?(A=void 0,e.stream.setBody(r.text),t.text=r.text,s.size>0&&p()):A=r.text)}),!0),sendFinalReply:r=>(N("final_reply",()=>{P.final+=1,isNonEmptyString(r.text)&&(!0!==r.isError?(A=void 0,e.stream.setBody(r.text),t.text=r.text,s.size>0&&p()):A=r.text)}),!0),waitForIdle:async()=>{for(let t=0;t<16;t++){if(await e.callbackQueue.waitForIdle(),0===g.size)return;await Promise.allSettled([...g])}await e.callbackQueue.waitForIdle()},getQueuedCounts:()=>({...P}),getAccumulatedBody:()=>t.text,getTerminalErrorText:()=>A,markComplete:t=>{if(1===l.length){const e=l.shift();k(f(),e.name,e.arguments,!1)}s.size>0&&p();for(const r of s.values())try{"success"===t&&b(r),y(r.id)}catch(t){const n=t instanceof Error?t.message:String(t);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${r.id} error=${n}`)}l.length=0,M=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,onAgentRunStart:t=>{e.onAgentRunStart?.(t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${t} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:r=>{N("partial",()=>{const n=extractBodyUpdate(t,r.text);switch(n.kind){case"append":e.stream.appendBody(n.text);break;case"replace":e.stream.setBody(n.text)}})},onReasoningStream:t=>{N("reasoning",()=>{if(!isNonEmptyString(t.text))return;if(!r&&(r=e.openReasoningStream?.(),!r))return;const s=t.text;""===o&&""!==n&&(r.appendBody("\n"),n+="\n"),s!==o&&(s.startsWith(o)?(r.appendBody(s.slice(o.length)),o=s):o.startsWith(s)||(r.setBody(n+s),o=s))})},onReasoningEnd:()=>{N("reasoning_end",()=>{var t;""!==o&&(t=o,e.stream.appendThinking(t,"done"),u+=1,n+=o,o="")})},onAssistantMessageStart:()=>{e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)},onToolStart:t=>{N("tool_start",()=>{const r="string"==typeof t.phase?t.phase:"start",n=resolveRuntimeToolName(t)??"tool",o=resolveRuntimeToolCallId(t);if("update"===r){if(o){const e=$(o);s.has(e)&&k(e,n,resolveRuntimeToolArguments(t),!0)}return}const i=resolveRuntimeToolArguments(t);if(o){const e=w(o,n,i);return e.toolStartObserved=!0,void(a.includes(e.id)||"streaming"!==e.state||a.push(e.id))}const d=(u=n,a.map(e=>s.get(e)).filter(e=>void 0!==e&&(!u||e.name===u)&&e.itemEventObserved));var u;if(1===d.length){return void(k(d[0].id,n,i,!0).toolStartObserved=!0)}d.length>1?e.logger.debug?.(`[im-sink] ambiguous id-less tool start ignored requestId=${e.requestId} channel=${e.channelId} tool=${n} candidates=${d.length}`):l.push({name:n,arguments:i})})},onItemEvent:e=>{N("item_event",()=>{const t="string"==typeof e.kind?e.kind.toLowerCase():"";if(t&&"tool"!==t)return;const r="string"==typeof e.phase?e.phase.toLowerCase():"update",n=resolveRuntimeToolCallId(e)??("string"==typeof e.itemId&&e.itemId.trim()?e.itemId.trim():void 0),o=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);if(!n||!o)return;if("update"===r){const e=$(n),t=s.get(e);return void(t&&(k(t.id,o,{},!0).itemEventObserved=!0))}const i=E(n,o);i.itemEventObserved=!0,a.includes(i.id)||"streaming"!==i.state||a.push(i.id)})}}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.6.48-staging.2",
3
+ "version": "0.6.48-staging.4",
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>",