beeos-claw 0.6.61 → 0.6.62-staging.1

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/README.md CHANGED
@@ -19,6 +19,7 @@ Add to your OpenClaw configuration (`~/.openclaw/openclaw.json`):
19
19
  "beeos-claw": {
20
20
  "package": "beeos-claw",
21
21
  "enabled": true,
22
+ "hooks": { "allowConversationAccess": true },
22
23
  "config": {
23
24
  "agentGatewayUrl": "https://api.beeos.ai",
24
25
  "bridge": {
@@ -212,6 +213,22 @@ See [`@beeos-ai/message-sdk` README](../../sdks/message-sdk-node/README.md#strea
212
213
 
213
214
  Provides the `beeos_upload_file` tool for agents to upload files from the sandbox to BeeOS platform storage. Also supports file staging via the `POST /beeos/files/stage` HTTP endpoint for downloading files from the platform into the agent sandbox.
214
215
 
216
+ The plugin records office/PDF paths produced by `write` / `exec` and uploads any
217
+ that were not already delivered. Tool-result enrichment attaches those files
218
+ during the turn; `before_agent_finalize` uploads leftovers without requesting
219
+ another model pass. Temporary files, source, and credentials are not scanned.
220
+ Non-bundled installations still need
221
+ `plugins.entries.beeos-claw.hooks.allowConversationAccess` (see Quick Start)
222
+ for the finalization hook to register.
223
+
224
+ Uploads compare SHA-256 of the exact bytes being sent. An unchanged local path
225
+ reuses a confirmed platform reference; edited contents create a new file ID and
226
+ leave previous attachments intact. Reuse is scoped to gateway, signing identity,
227
+ absolute path, and output name. The bounded in-memory cache retains up to 256
228
+ entries for 30 minutes; expiry or restart permits a fresh upload. Failures are
229
+ not cached. Reused results still contain normal file/resource-link blocks so the
230
+ current conversation receives the attachment.
231
+
215
232
  ### Terminal Bridge
216
233
 
217
234
  When `bridge.shell.enabled` is `true`, the plugin starts a WebSocket server for web-SSH / terminal access. Remote users can open terminal sessions through the Bridge connection.
@@ -1 +1 @@
1
- import{presentFileArtifacts}from"./file-artifact-presenter.js";import{buildToolCallUpdatePayload}from"./tool-call-payload-builder.js";import{normalizeToolResultForAcpUpdate}from"./tool-result-payload-strategies.js";export async function emitNormalizedToolResultForTurn(t){const{session:e,envelope:o}=t;return emitToolResultProjectionForTurn({session:e,projection:{...(await normalizeToolResultForAcpUpdate({toolCallId:t.normalizeToolCallId??o.toolCallId,toolName:o.toolName,rawText:o.rawText,rawResult:o.rawResult})).projection,toolName:o.toolName,rawOutput:o.rawResult,...o.timestamp?{timestamp:o.timestamp}:{}},toolCallId:o.toolCallId,config:t.config,logger:t.logger,writeObs:t.writeObs,getCanvasState:t.getCanvasState,turnId:t.turnId,sendUpdate:t.sendUpdate})}export async function emitToolResultProjectionForTurn(t){const{session:e,projection:o,toolCallId:r}=t,l={emitted:!1,outputText:o.outputText,...o.resourceLinks?.length?{resourceLinks:o.resourceLinks}:{}};if(!o.outputText&&!o.resourceLinks?.length)return l;const a=`${r}:${o.projectionKey}`;if(e.emittedToolResultKeys??=new Set,e.emittedToolResultKeys.has(a))return l;const s=buildToolCallUpdatePayload({toolCallId:r,toolName:o.toolName,outputText:o.outputText,rawOutput:o.rawOutput,resourceLinks:o.resourceLinks,timestamp:o.timestamp});stampTurnId(s,t.turnId);return!1!==t.sendUpdate(s)?(e.emittedToolResultKeys.add(a),l.emitted=!0,t.writeObs({component:"acp-gateway",domain:"tool_result",name:"tool_result.normalized",severity:"info",payload:{toolCallId:r,toolName:o.toolName,hasText:Boolean(o.outputText),resourceLinkCount:o.resourceLinks?.length??0,projectionKey:o.projectionKey}}),o.resourceLinks?.length&&await presentFileArtifacts(e,r,o.resourceLinks,{config:t.config,logger:t.logger,writeObs:t.writeObs,getCanvasState:t.getCanvasState,sendUpdate:e=>{stampTurnId(e,t.turnId),t.sendUpdate(e)}}),l):l}function stampTurnId(t,e){e&&void 0===t._turnId&&(t._turnId=e)}
1
+ import{presentFileArtifacts}from"./file-artifact-presenter.js";import{buildToolCallUpdatePayload}from"./tool-call-payload-builder.js";import{normalizeToolResultForAcpUpdate}from"./tool-result-payload-strategies.js";export async function emitNormalizedToolResultForTurn(t){const{session:e,envelope:o}=t;return emitToolResultProjectionForTurn({session:e,projection:{...(await normalizeToolResultForAcpUpdate({toolCallId:t.normalizeToolCallId??o.toolCallId,toolName:o.toolName,rawText:o.rawText,rawResult:o.rawResult,cwd:e.cwd})).projection,toolName:o.toolName,rawOutput:o.rawResult,...o.timestamp?{timestamp:o.timestamp}:{}},toolCallId:o.toolCallId,config:t.config,logger:t.logger,writeObs:t.writeObs,getCanvasState:t.getCanvasState,turnId:t.turnId,sendUpdate:t.sendUpdate})}export async function emitToolResultProjectionForTurn(t){const{session:e,projection:o,toolCallId:r}=t,l={emitted:!1,outputText:o.outputText,...o.resourceLinks?.length?{resourceLinks:o.resourceLinks}:{}};if(!o.outputText&&!o.resourceLinks?.length)return l;const a=`${r}:${o.projectionKey}`;if(e.emittedToolResultKeys??=new Set,e.emittedToolResultKeys.has(a))return l;const s=buildToolCallUpdatePayload({toolCallId:r,toolName:o.toolName,outputText:o.outputText,rawOutput:o.rawOutput,resourceLinks:o.resourceLinks,timestamp:o.timestamp});stampTurnId(s,t.turnId);return!1!==t.sendUpdate(s)?(e.emittedToolResultKeys.add(a),l.emitted=!0,t.writeObs({component:"acp-gateway",domain:"tool_result",name:"tool_result.normalized",severity:"info",payload:{toolCallId:r,toolName:o.toolName,hasText:Boolean(o.outputText),resourceLinkCount:o.resourceLinks?.length??0,projectionKey:o.projectionKey}}),o.resourceLinks?.length&&await presentFileArtifacts(e,r,o.resourceLinks,{config:t.config,logger:t.logger,writeObs:t.writeObs,getCanvasState:t.getCanvasState,sendUpdate:e=>{stampTurnId(e,t.turnId),t.sendUpdate(e)}}),l):l}function stampTurnId(t,e){e&&void 0===t._turnId&&(t._turnId=e)}
@@ -1 +1 @@
1
- import{readUploadToolResult}from"../upload/result-cache.js";import{BEEOS_UPLOAD_TOOL_NAME}from"../upload/tool.js";import{isUploadArtifactToolName,resolveToolArtifactLinks}from"../upload/tool-artifact-enrichment.js";import{isPlainRecord as isRecord}from"../utils/text.js";import{buildToolResultSessionContentBlocks}from"./tool-result-blocks.js";import{projectBlocksToToolResultProjection}from"./tool-result-projection.js";export{buildToolResultSessionContentBlocks}from"./tool-result-blocks.js";const strategies=[{name:"beeos_upload_file_cache",canHandle:t=>t.toolName===BEEOS_UPLOAD_TOOL_NAME,resolve:strategyUploadCache},{name:"passthrough_gateway_result",canHandle:()=>!0,resolve:async t=>strategyPassthrough(t)}];export function registerToolResultStrategy(t,o){if(o?.prepend)strategies.unshift(t);else{const o=Math.max(0,strategies.length-1);strategies.splice(o,0,t)}}export function getToolResultStrategies(){return strategies}export async function resolveToolResultBlocks(t){let o=[];for(const e of strategies)if(e.canHandle(t))try{const l=await e.resolve(t);if(l&&l.length>0){o=l;break}}catch{}return enrichWithLocalFileUploads(t,o)}export async function normalizeToolResultForAcpUpdate(t){let o;try{o=await resolveToolResultBlocks(t)}catch{o=t.rawText?[{type:"content",content:{type:"text",text:t.rawText}}]:[]}0===o.length&&t.rawText&&(o=[{type:"content",content:{type:"text",text:t.rawText}}]);const e=projectBlocksToToolResultProjection({toolCallId:t.toolCallId,toolName:t.toolName,blocks:o,rawOutput:t.rawResult});return{blocks:o,outputText:e.outputText,rawOutput:t.rawResult,...e.resourceLinks?.length?{resourceLinks:e.resourceLinks}:{},projection:e}}async function enrichWithLocalFileUploads(t,o){let e;try{e=await resolveToolArtifactLinks({toolCallId:t.toolCallId,toolName:t.toolName,rawResult:t.rawResult,rawText:t.rawText})}catch{return o}if(0===e.length)return o;const l=new Set;for(const t of o){if("content"!==t.type)continue;const o=t.content;"resource_link"===o?.type&&"string"==typeof o.uri&&l.add(o.uri)}const r=[...o],s=!isUploadArtifactToolName(t.toolName);for(const t of e)l.has(t.uri)||(r.push({type:"content",content:{type:"resource_link",...t}}),s&&r.push({type:"content",content:{type:"text",text:`[Auto-uploaded: ${t.name}. Do NOT call beeos_upload_file for this file.]`}}));return r}async function strategyUploadCache(t){if(t.toolName!==BEEOS_UPLOAD_TOOL_NAME)return null;let o;try{o=await readUploadToolResult(t.toolCallId)}catch{return null}if(!o)return null;const e=pickUploadPayload(o);return buildToolResultSessionContentBlocks(t.toolName,e??o)}function strategyPassthrough(t){if(t.rawResult&&isRecord(t.rawResult)){const o=buildToolResultSessionContentBlocks(t.toolName,t.rawResult);if(o.length>0)return o}const o=t.rawText?.trim();if(!o)return[];try{const e=JSON.parse(o);if(isRecord(e)){const o=buildToolResultSessionContentBlocks(t.toolName,e);if(o.length>0)return o}}catch{}return[{type:"content",content:{type:"text",text:o}}]}function isUploadPayload(t){return!!isRecord(t)&&(!0===t.ok||!1===t.ok||"files"in t||Array.isArray(t.content)&&t.content.length>0)}function pickUploadPayload(t){if(isRecord(t)){for(const o of["output","result","details"]){const e=t[o];if(isUploadPayload(e))return e;if(isRecord(e))for(const t of["output","result","details"])if(isUploadPayload(e[t]))return e[t]}return Array.isArray(t.content)&&t.content.length>0||isUploadPayload(t)?t:void 0}}
1
+ import{readUploadToolResult}from"../upload/result-cache.js";import{BEEOS_UPLOAD_TOOL_NAME}from"../upload/tool.js";import{isUploadArtifactToolName,resolveToolArtifactLinks}from"../upload/tool-artifact-enrichment.js";import{isPlainRecord as isRecord}from"../utils/text.js";import{buildToolResultSessionContentBlocks}from"./tool-result-blocks.js";import{projectBlocksToToolResultProjection}from"./tool-result-projection.js";export{buildToolResultSessionContentBlocks}from"./tool-result-blocks.js";const strategies=[{name:"beeos_upload_file_cache",canHandle:t=>t.toolName===BEEOS_UPLOAD_TOOL_NAME,resolve:strategyUploadCache},{name:"passthrough_gateway_result",canHandle:()=>!0,resolve:async t=>strategyPassthrough(t)}];export function registerToolResultStrategy(t,o){if(o?.prepend)strategies.unshift(t);else{const o=Math.max(0,strategies.length-1);strategies.splice(o,0,t)}}export function getToolResultStrategies(){return strategies}export async function resolveToolResultBlocks(t){let o=[];for(const e of strategies)if(e.canHandle(t))try{const l=await e.resolve(t);if(l&&l.length>0){o=l;break}}catch{}return enrichWithLocalFileUploads(t,o)}export async function normalizeToolResultForAcpUpdate(t){let o;try{o=await resolveToolResultBlocks(t)}catch{o=t.rawText?[{type:"content",content:{type:"text",text:t.rawText}}]:[]}0===o.length&&t.rawText&&(o=[{type:"content",content:{type:"text",text:t.rawText}}]);const e=projectBlocksToToolResultProjection({toolCallId:t.toolCallId,toolName:t.toolName,blocks:o,rawOutput:t.rawResult});return{blocks:o,outputText:e.outputText,rawOutput:t.rawResult,...e.resourceLinks?.length?{resourceLinks:e.resourceLinks}:{},projection:e}}async function enrichWithLocalFileUploads(t,o){let e;try{e=await resolveToolArtifactLinks({toolCallId:t.toolCallId,toolName:t.toolName,rawResult:t.rawResult,rawText:t.rawText,params:t.params,cwd:t.cwd})}catch{return o}if(0===e.length)return o;const l=new Set;for(const t of o){if("content"!==t.type)continue;const o=t.content;"resource_link"===o?.type&&"string"==typeof o.uri&&l.add(o.uri)}const r=[...o],s=!isUploadArtifactToolName(t.toolName);for(const t of e)l.has(t.uri)||(r.push({type:"content",content:{type:"resource_link",...t}}),s&&r.push({type:"content",content:{type:"text",text:`[Auto-uploaded: ${t.name}. Do NOT call beeos_upload_file for this file.]`}}));return r}async function strategyUploadCache(t){if(t.toolName!==BEEOS_UPLOAD_TOOL_NAME)return null;let o;try{o=await readUploadToolResult(t.toolCallId)}catch{return null}if(!o)return null;const e=pickUploadPayload(o);return buildToolResultSessionContentBlocks(t.toolName,e??o)}function strategyPassthrough(t){if(t.rawResult&&isRecord(t.rawResult)){const o=buildToolResultSessionContentBlocks(t.toolName,t.rawResult);if(o.length>0)return o}const o=t.rawText?.trim();if(!o)return[];try{const e=JSON.parse(o);if(isRecord(e)){const o=buildToolResultSessionContentBlocks(t.toolName,e);if(o.length>0)return o}}catch{}return[{type:"content",content:{type:"text",text:o}}]}function isUploadPayload(t){return!!isRecord(t)&&(!0===t.ok||!1===t.ok||"files"in t||Array.isArray(t.content)&&t.content.length>0)}function pickUploadPayload(t){if(isRecord(t)){for(const o of["output","result","details"]){const e=t[o];if(isUploadPayload(e))return e;if(isRecord(e))for(const t of["output","result","details"])if(isUploadPayload(e[t]))return e[t]}return Array.isArray(t.content)&&t.content.length>0||isUploadPayload(t)?t:void 0}}
@@ -1 +1 @@
1
- import{extractBeeosConversationIdFromSessionKey}from"../runtime/openclaw-session-key-resolution.js";const A2UI_TYPE_NAMES=new Set(["Text","Image","Icon","Video","AudioPlayer","Row","Column","List","Card","Tabs","Divider","Modal","Button","CheckBox","TextField","DateTimeInput","MultipleChoice","Slider","Chart","WebEmbed","FilePreview","MapView"]);function normalizeComponents(e){return e.map(e=>{if(!e||"object"!=typeof e)return e;const t=e;if(t.component&&"object"==typeof t.component)return t;for(const e of Object.keys(t))if(A2UI_TYPE_NAMES.has(e)&&"object"==typeof t[e])return{id:t.id,component:{[e]:t[e]}};return t})}export const RENDER_UI_TOOL_NAME="render_ui";export const CLOSE_UI_TOOL_NAME="close_ui";export const RENDER_UI_TOOL_DESCRIPTION="Render A2UI v0.8 components to the Canvas panel. Prefer this over the built-in canvas tool.";export const RENDER_UI_TOOL_SCHEMA={type:"object",required:["id","root","components"],properties:{id:{type:"string",description:"Surface id. Reuse to update."},root:{type:"string",description:"Root component id."},components:{type:"array",description:"Flat ComponentInstance list. Each item has id plus one component type.",items:{type:"object",required:["id"],properties:{id:{type:"string",description:"Component id."},component:{type:"object",description:"One type key: Text, Image, Icon, Video, AudioPlayer, Row, Column, List, Card, Tabs, Divider, Modal, Button, CheckBox, TextField, DateTimeInput, MultipleChoice, Slider, Chart."}}}},data:{type:"object",description:"Optional binding model, path keys like /name."}}};export const CLOSE_UI_TOOL_DESCRIPTION="Close and remove a UI surface that was previously rendered.";export const CLOSE_UI_TOOL_SCHEMA={type:"object",required:["id"],properties:{id:{type:"string",description:"The UI identifier to close"}}};const SESSION_READY_POLL_ATTEMPTS=3,SESSION_READY_POLL_INTERVAL_MS=400,delay=e=>new Promise(t=>setTimeout(t,e));async function ensureRelayConnected(e,t,n,a){const{stateManager:o,logger:i}=e,r=a??e.getGatewaySessionKey?.(n),s=r??t,l=o.resolveCanvasForSession(s);if(l&&o.isCanvasJoined(l))return l;let c=o.getRelayClient();if(c||(c=await o.waitForRelayClient(),c))try{e.waitForAgentSync&&await e.waitForAgentSync();const a=e.getPlatformAgentId?.(n),l=r??t,d=(r?extractBeeosConversationIdFromSessionKey(r):void 0)??(r&&r!==t?t:void 0),u=await c.ensureCanvas(l,{agentId:a,linkSessionId:d});return await o.joinCanvas(u,s),i.info?.(`[canvas] Joined canvas=${u} session=${t} gatewayKey=${r??"none"} agentId=${a??"none"}`),u}catch(e){return void i.warn?.(`[canvas] Failed to join canvas relay: ${e}`)}}export function createRenderUiHandler(e){const{stateManager:t,logger:n,writeObs:a,isCanvasActive:o,getGatewaySessionKey:i}=e;let r=0;return async(s,l)=>{if(o){const e=i?.(s);if(!o(e))return{success:!1,error:"Canvas is disabled by user. Respond with plain text instead."}}const c=l,d=c.id,u=c.root,p="panel";let m=c.components;if("string"==typeof m)try{m=JSON.parse(m)}catch{}if(!(d&&u&&Array.isArray(m)&&m.length))return{success:!1,error:"id, root, and components (array) are required"};const h=normalizeComponents(m);if(!t.validateComponentCount(h))return{success:!1,error:"Too many components (max 500)"};if(!t.checkRateLimit(d))return n.warn?.(`[canvas] Rate limited surfaceId=${d}`),a({component:"canvas",domain:"canvas",name:"canvas.rate_limited",severity:"warn",payload:{surfaceId:d}}),{success:!1,error:"Rate limited — too many updates per second"};const y=c.data;let f=e.getActiveSessionId?.(s);for(let t=0;!f&&t<3;t++)await delay(400),f=e.getActiveSessionId?.(s);if(!f&&e.waitForAgentSync){try{await e.waitForAgentSync()}catch{}f=e.getActiveSessionId?.(s)}if(!f)return n.warn?.(`[canvas] No active session for toolCallId=${s} after 3 polls, render_ui not ready`),{success:!1,retryable:!0,error:"Canvas session not ready yet (agent just connected) — retry render_ui shortly; do NOT fall back to plain text."};const v=e.getGatewaySessionKey?.(s),g=await ensureRelayConnected(e,f,s,v),C=t.has(d,g??void 0),b=Date.now(),w=t.getRelayClient();g&&w&&b-r>6e4&&w.createSnapshot(g,"Pre-agent edit").then(()=>{r=Date.now(),n.debug?.("[canvas] Pre-edit checkpoint created")}).catch(e=>n.warn?.(`[canvas] Pre-edit checkpoint failed: ${e}`)),C?t.update(d,h,g,u,y):t.create(g??`_fallback_${Date.now()}`,d,p,h,{origin:{kind:"agent"},rootComponentId:u,dataModel:y});const x=h.map(e=>e.component&&"object"==typeof e.component?Object.keys(e.component)[0]??"unknown":"unknown");a({component:"canvas",domain:"canvas",name:C?"canvas.update_sent":"canvas.surface_created",severity:"info",payload:{surfaceId:d,presentation:p,componentCount:h.length,componentTypes:x}}),n.debug?.(`[canvas] ${C?"Updated":"Created"} surface=${d} components=${h.length}`);const T={success:!0,surfaceId:d,presentation:p,isUpdate:C,activeSurfaces:g?t.getAllActive(g).map(e=>e.surfaceId):[d]};return y&&(T.dataModel=y),T}}export function createCloseUiHandler(e){const{stateManager:t,logger:n,writeObs:a}=e;return async(o,i)=>{const r=i.id;if(!r)return{success:!1,error:"id is required"};const s=t.get(r),l=s?Date.now()-s.createdAt:0,c=e.getGatewaySessionKey?.(o),d=c?t.resolveCanvasForSession(c):void 0;return t.delete(r,d),a({component:"canvas",domain:"canvas",name:"canvas.surface_closed",severity:"info",payload:{surfaceId:r,lifetime_ms:l}}),n.debug?.(`[canvas] Closed surface=${r}`),{success:!0,surfaceId:r,message:"UI closed"}}}export const CANVAS_PRESENTATION_GATE="## BeeOS Canvas presentation gate\n\nCanvas presentation is available for this BeeOS conversation. Whether the Canvas panel is currently open is checked when a Canvas tool is called.\n\nDo not read `beeos-canvas/SKILL.md` or any canvas skill file. Call `render_ui` directly. The A2UI schema is in this prompt and the `render_ui` tool.\n\nAfter any skill or data tool returns, apply this gate before writing the final reply:\n1. Count the related records, items, rows, time points, options, or repeated field groups in the completed result.\n2. If there are two or more, you MUST call `render_ui` before the final reply.\n3. Do not replace `render_ui` with a Markdown table, bullets, JSON, or a prose summary.\n4. If there is only one fact or value, or the answer is a prose explanation, reply in chat without `render_ui`.\n\nDomain skills control how data is obtained and interpreted. This gate controls how the completed result is presented and overrides a domain skill's default text-summary format. When `render_ui` succeeds, keep the chat reply to one short confirmation sentence.";export const CANVAS_SYSTEM_PROMPT='## Canvas UI — Google A2UI v0.8\n\n**IMPORTANT: Always use `render_ui` (not the built-in `canvas` tool) to display visual/structured content.**\n\n### How render_ui works\n\nYou output a **flat array of components** with unique IDs. Components reference each other by ID.\n\nParameters:\n- `id`: Surface ID (e.g. "sales-report"). Reuse same id to update.\n- `root`: The ID of the top-level component (usually a Column).\n- `components`: Flat array of objects. **Each object MUST have `id` and `component`**:\n `{"id": "myId", "component": {"Text": {"text": {"literalString": "Hello"}, "usageHint": "body"}}}`\n The `component` key wraps exactly ONE type key (Text, Card, Row, etc.) and its properties.\n- `data` (optional): Data model object for form two-way binding.\n\n### Rules\n- Keep chat reply to ONE short sentence when using render_ui. Do NOT repeat data shown in canvas.\n- Use render_ui for structured data, visualizations, forms, dashboards. Use plain text for explanations.\n- Canvas renders in a narrow sidebar (~400px).\n- Prefer fewer, larger components. One table is better than many small cards.\n- Use descriptive surface IDs (e.g. "sales-report", not "ui1").\n- Same `id` updates existing surface; different `id` creates new one.\n- When asked to modify existing content, reuse the original surface id from [Canvas State].\n\n### StringValue\n\nText properties use StringValue — an object with exactly one key:\n- Static text: `{"literalString": "Hello"}`\n- Data binding: `{"path": "/fieldName"}` (reads from the `data` model)\n\nShorthand: `sv("text")` means `{"literalString": "text"}` in examples below.\n\n### Component Reference\n\n**Text** — Display text with semantic sizing.\n`{Text: {text: sv("Title"), usageHint: "h2"}}`\nusageHint: "h1" | "h2" | "h3" | "h4" | "h5" | "caption" | "body"\ntext also renders Markdown (bold, italic, lists, code blocks, links).\n\n**Image** — Display an image.\n`{Image: {url: sv("https://..."), usageHint: "mediumFeature"}}`\nImage.url may be `beeos-file://<fileId>`; the renderer resolves it. Do not convert it to a signed URL.\nusageHint: "icon" | "avatar" | "smallFeature" | "mediumFeature" | "largeFeature" | "header"\nfit (optional): "contain" | "cover" | "fill" | "none" | "scale-down"\n\n**Icon** — Material icon.\n`{Icon: {name: sv("check")}}`\n\n**Video** — Embed a video.\n`{Video: {url: sv("https://youtube.com/...")}}`\n\n**AudioPlayer** — Audio playback.\n`{AudioPlayer: {url: sv("https://..."), description: sv("Episode 1")}}`\n\n**Row** — Horizontal layout. Children referenced by ID.\n`{Row: {children: {explicitList: ["child1", "child2"]}, distribution: "spaceBetween"}}`\ndistribution: "start" | "center" | "end" | "spaceBetween" | "spaceAround" | "spaceEvenly"\nalignment: "start" | "center" | "end" | "stretch"\n\n**Column** — Vertical layout. Children referenced by ID.\n`{Column: {children: {explicitList: ["child1", "child2"]}}}`\nSame distribution/alignment options as Row.\n\n**List** — Vertical or horizontal list.\n`{List: {children: {explicitList: ["item1", "item2"]}, direction: "vertical"}}`\n\n**Card** — Container with a single child.\n`{Card: {child: "contentColumnId"}}`\n\n**Tabs** — Tabbed container.\n`{Tabs: {tabItems: [{title: sv("Tab 1"), child: "panel1"}, {title: sv("Tab 2"), child: "panel2"}]}}`\n\n**Divider** — Horizontal or vertical separator.\n`{Divider: {axis: "horizontal"}}`\n\n**Modal** — Dialog with trigger.\n`{Modal: {entryPointChild: "triggerBtnId", contentChild: "dialogContentId"}}`\n\n**Button** — Clickable with action.\n`{Button: {child: "labelTextId", action: {name: "onClick", context: [{key: "id", value: {literalString: "item-1"}}]}}}`\n\n**TextField** — Text input with data binding.\n`{TextField: {label: sv("Name"), text: {path: "/name"}, type: "shortText"}}`\ntype: "shortText" | "number" | "date" | "longText"\n\n**CheckBox** — Boolean toggle.\n`{CheckBox: {label: sv("Agree"), value: {path: "/agree"}}}`\n\n**DateTimeInput** — Date/time picker.\n`{DateTimeInput: {value: {path: "/date"}, enableDate: true, enableTime: false}}`\n\n**MultipleChoice** — Selection from options.\n`{MultipleChoice: {selections: {path: "/country"}, options: [{label: sv("US"), value: "US"}, {label: sv("CN"), value: "CN"}]}}`\n\n**Slider** — Numeric range input.\n`{Slider: {value: {path: "/volume"}, minValue: 0, maxValue: 100}}`\n\n**Chart** (BeeOS extension) — Data visualization.\n`{Chart: {chartType: sv("bar"), title: sv("Sales"), labels: ["Q1","Q2","Q3"], datasets: [{label: "Revenue", data: [100,200,150]}]}}`\nchartType: bar, horizontal_bar, line, area, scatter, pie, doughnut, radar, gauge, candlestick, funnel, sparkline\ngauge uses value + max instead of labels/datasets.\ncandlestick data: [{open,high,low,close}] (full field names, NOT o/h/l/c).\n\n### Composition\n\nNest by id: Column/Row `children.explicitList`, Card `child`. Example:\n`{"id":"s1","root":"r","components":[{"id":"t","component":{"Text":{"text":{"literalString":"Hello"},"usageHint":"h2"}}},{"id":"r","component":{"Column":{"children":{"explicitList":["t"]}}}}]}`\nTables: Card > Column > Row > Text. Forms: TextField/CheckBox + Button `action.context` paths.\n\n### render_form\nUse `render_form` for schema-driven forms when a domain schema exists.\nParameters: `domain`, `operation`, `data`, `entityId`.\n\n### close_ui\nCloses a UI surface. Use when user moves to a new topic or explicitly dismisses.\n';export const CANVAS_TERM_MAPPING='[System Note] Always use `render_ui` (not the canvas tool) for visual/structured content. Each component in the array MUST be: {"id":"x","component":{"TypeName":{props}}} — the "component" wrapper key is required. render_ui uses Google A2UI v0.8 format: flat component array with id references. Components: Text (usageHint: h1-h5/caption/body), Image, Icon, Video, AudioPlayer, Row (horizontal layout, children.explicitList), Column (vertical layout, children.explicitList), List, Card (single child), Tabs (tabItems), Divider, Modal, Button (child + action), TextField, CheckBox, MultipleChoice, Slider, DateTimeInput, Chart (BeeOS extension: chartType, labels, datasets). StringValue: {"literalString":"text"} or {"path":"/binding"}. root param = top-level component ID. Tables: Card > Column > Row > Text. Forms: TextField/CheckBox/Slider + Button with action.context paths. Chart: chartType(bar/line/pie/area/scatter/doughnut/radar/funnel/gauge/candlestick/sparkline), labels, datasets:[{label,data,color?}]. Candlestick: [{open,high,low,close}]. Gauge: value+max. Keep chat brief when canvas is used. Use `close_ui` to dismiss. Reuse same id to update; new id for new content.';export const CANVAS_UPDATE_HINT="[Canvas Rule] Same id in render_ui = update existing surface. Different id = new surface. When a surface is referenced for editing, you MUST reuse its exact id and provide the full updated component tree.";const MAX_CONTEXT_SURFACES=8,MAX_CONTEXT_CHARS=500;export function buildActiveSurfacesContext(e,t){let n;if(!t)return null;if(n=e.getAllActive(t),0===n.length)return null;const a=[...n].sort((e,t)=>t.updatedAt-e.updatedAt).slice(0,8),o=n.length-a.length;let i=`[Canvas State] Active surfaces: ${a.map(t=>{const n=e.summarize(t.surfaceId);if(!n)return`"${t.surfaceId}"`;const a=n.types.slice(0,3).join(", "),o=n.title?`: "${n.title}"`:"";return`"${t.surfaceId}" (${a}${o})`}).join("; ")}.`;return o>0&&(i+=` ...and ${o} more.`),i+=" To modify existing content, reuse its id in render_ui.",i.length>500&&(i=i.slice(0,497)+"..."),i}
1
+ import{extractBeeosConversationIdFromSessionKey}from"../runtime/openclaw-session-key-resolution.js";const A2UI_TYPE_NAMES=new Set(["Text","Image","Icon","Video","AudioPlayer","Row","Column","List","Card","Tabs","Divider","Modal","Button","CheckBox","TextField","DateTimeInput","MultipleChoice","Slider","Chart","WebEmbed","FilePreview","MapView"]);function normalizeComponents(e){return e.map(e=>{if(!e||"object"!=typeof e)return e;const t=e;if(t.component&&"object"==typeof t.component)return t;for(const e of Object.keys(t))if(A2UI_TYPE_NAMES.has(e)&&"object"==typeof t[e])return{id:t.id,component:{[e]:t[e]}};return t})}export const RENDER_UI_TOOL_NAME="render_ui";export const CLOSE_UI_TOOL_NAME="close_ui";export const RENDER_UI_TOOL_DESCRIPTION="Render A2UI v0.8 components to the Canvas panel. Prefer this over the built-in canvas tool.";export const RENDER_UI_TOOL_SCHEMA={type:"object",required:["id","root","components"],properties:{id:{type:"string",description:"Surface id. Reuse to update."},root:{type:"string",description:"Root component id."},components:{type:"array",description:"Flat ComponentInstance list. Each item has id plus one component type.",items:{type:"object",required:["id"],properties:{id:{type:"string",description:"Component id."},component:{type:"object",description:"One type key: Text, Image, Icon, Video, AudioPlayer, Row, Column, List, Card, Tabs, Divider, Modal, Button, CheckBox, TextField, DateTimeInput, MultipleChoice, Slider, Chart."}}}},data:{type:"object",description:"Optional binding model, path keys like /name."}}};export const CLOSE_UI_TOOL_DESCRIPTION="Close and remove a UI surface that was previously rendered.";export const CLOSE_UI_TOOL_SCHEMA={type:"object",required:["id"],properties:{id:{type:"string",description:"The UI identifier to close"}}};const SESSION_READY_POLL_ATTEMPTS=3,SESSION_READY_POLL_INTERVAL_MS=400,delay=e=>new Promise(t=>setTimeout(t,e));async function ensureRelayConnected(e,t,n,a){const{stateManager:o,logger:i}=e,r=a??e.getGatewaySessionKey?.(n),s=r??t,l=o.resolveCanvasForSession(s);if(l&&o.isCanvasJoined(l))return l;let c=o.getRelayClient();if(c||(c=await o.waitForRelayClient(),c))try{e.waitForAgentSync&&await e.waitForAgentSync();const a=e.getPlatformAgentId?.(n),l=r??t,d=(r?extractBeeosConversationIdFromSessionKey(r):void 0)??(r&&r!==t?t:void 0),u=await c.ensureCanvas(l,{agentId:a,linkSessionId:d});return await o.joinCanvas(u,s),i.info?.(`[canvas] Joined canvas=${u} session=${t} gatewayKey=${r??"none"} agentId=${a??"none"}`),u}catch(e){return void i.warn?.(`[canvas] Failed to join canvas relay: ${e}`)}}export function createRenderUiHandler(e){const{stateManager:t,logger:n,writeObs:a,getGatewaySessionKey:o}=e;let i=0;return async(o,r)=>{const s=r,l=s.id,c=s.root,d="panel";let u=s.components;if("string"==typeof u)try{u=JSON.parse(u)}catch{}if(!(l&&c&&Array.isArray(u)&&u.length))return{success:!1,error:"id, root, and components (array) are required"};const p=normalizeComponents(u);if(!t.validateComponentCount(p))return{success:!1,error:"Too many components (max 500)"};if(!t.checkRateLimit(l))return n.warn?.(`[canvas] Rate limited surfaceId=${l}`),a({component:"canvas",domain:"canvas",name:"canvas.rate_limited",severity:"warn",payload:{surfaceId:l}}),{success:!1,error:"Rate limited — too many updates per second"};const m=s.data;let h=e.getActiveSessionId?.(o);for(let t=0;!h&&t<3;t++)await delay(400),h=e.getActiveSessionId?.(o);if(!h&&e.waitForAgentSync){try{await e.waitForAgentSync()}catch{}h=e.getActiveSessionId?.(o)}if(!h)return n.warn?.(`[canvas] No active session for toolCallId=${o} after 3 polls, render_ui not ready`),{success:!1,retryable:!0,error:"Canvas session not ready yet (agent just connected) — retry render_ui shortly; do NOT fall back to plain text."};const y=e.getGatewaySessionKey?.(o),f=await ensureRelayConnected(e,h,o,y),g=t.has(l,f??void 0),v=Date.now(),C=t.getRelayClient();f&&C&&v-i>6e4&&C.createSnapshot(f,"Pre-agent edit").then(()=>{i=Date.now(),n.debug?.("[canvas] Pre-edit checkpoint created")}).catch(e=>n.warn?.(`[canvas] Pre-edit checkpoint failed: ${e}`)),g?t.update(l,p,f,c,m):t.create(f??`_fallback_${Date.now()}`,l,d,p,{origin:{kind:"agent"},rootComponentId:c,dataModel:m});const b=p.map(e=>e.component&&"object"==typeof e.component?Object.keys(e.component)[0]??"unknown":"unknown");a({component:"canvas",domain:"canvas",name:g?"canvas.update_sent":"canvas.surface_created",severity:"info",payload:{surfaceId:l,presentation:d,componentCount:p.length,componentTypes:b}}),n.debug?.(`[canvas] ${g?"Updated":"Created"} surface=${l} components=${p.length}`);const w={success:!0,surfaceId:l,presentation:d,isUpdate:g,activeSurfaces:f?t.getAllActive(f).map(e=>e.surfaceId):[l]};return m&&(w.dataModel=m),w}}export function createCloseUiHandler(e){const{stateManager:t,logger:n,writeObs:a}=e;return async(o,i)=>{const r=i.id;if(!r)return{success:!1,error:"id is required"};const s=t.get(r),l=s?Date.now()-s.createdAt:0,c=e.getGatewaySessionKey?.(o),d=c?t.resolveCanvasForSession(c):void 0;return t.delete(r,d),a({component:"canvas",domain:"canvas",name:"canvas.surface_closed",severity:"info",payload:{surfaceId:r,lifetime_ms:l}}),n.debug?.(`[canvas] Closed surface=${r}`),{success:!0,surfaceId:r,message:"UI closed"}}}export const CANVAS_PRESENTATION_GATE="## BeeOS Canvas presentation gate\n\nCanvas presentation is available in the background for this BeeOS conversation. The Canvas resource tab does not need to be open and no Web subscriber is required before a Canvas tool is called. Describe successful rendering as content created or updated on the canvas; do not claim that a user is currently viewing it.\n\nDo not read `beeos-canvas/SKILL.md` or any canvas skill file. Call `render_ui` directly. The A2UI schema is in this prompt and the `render_ui` tool.\n\nAfter any skill or data tool returns, apply this gate before writing the final reply:\n1. Count the related records, items, rows, time points, options, or repeated field groups in the completed result.\n2. If there are two or more, you MUST call `render_ui` before the final reply.\n3. Do not replace `render_ui` with a Markdown table, bullets, JSON, or a prose summary.\n4. If there is only one fact or value, or the answer is a prose explanation, reply in chat without `render_ui`.\n\nDomain skills control how data is obtained and interpreted. This gate controls how the completed result is presented and overrides a domain skill's default text-summary format. When `render_ui` succeeds, keep the chat reply to one short confirmation sentence.";export const CANVAS_SYSTEM_PROMPT='## Canvas UI — Google A2UI v0.8\n\n**IMPORTANT: Always use `render_ui` (not the built-in `canvas` tool) to display visual/structured content.**\n\n### How render_ui works\n\nYou output a **flat array of components** with unique IDs. Components reference each other by ID.\n\nParameters:\n- `id`: Surface ID (e.g. "sales-report"). Reuse same id to update.\n- `root`: The ID of the top-level component (usually a Column).\n- `components`: Flat array of objects. **Each object MUST have `id` and `component`**:\n `{"id": "myId", "component": {"Text": {"text": {"literalString": "Hello"}, "usageHint": "body"}}}`\n The `component` key wraps exactly ONE type key (Text, Card, Row, etc.) and its properties.\n- `data` (optional): Data model object for form two-way binding.\n\n### Rules\n- Keep chat reply to ONE short sentence when using render_ui. Do NOT repeat data shown in canvas.\n- Use render_ui for structured data, visualizations, forms, dashboards. Use plain text for explanations.\n- Canvas renders in a narrow sidebar (~400px).\n- Prefer fewer, larger components. One table is better than many small cards.\n- Use descriptive surface IDs (e.g. "sales-report", not "ui1").\n- Same `id` updates existing surface; different `id` creates new one.\n- When asked to modify existing content, reuse the original surface id from [Canvas State].\n\n### StringValue\n\nText properties use StringValue — an object with exactly one key:\n- Static text: `{"literalString": "Hello"}`\n- Data binding: `{"path": "/fieldName"}` (reads from the `data` model)\n\nShorthand: `sv("text")` means `{"literalString": "text"}` in examples below.\n\n### Component Reference\n\n**Text** — Display text with semantic sizing.\n`{Text: {text: sv("Title"), usageHint: "h2"}}`\nusageHint: "h1" | "h2" | "h3" | "h4" | "h5" | "caption" | "body"\ntext also renders Markdown (bold, italic, lists, code blocks, links).\n\n**Image** — Display an image.\n`{Image: {url: sv("https://..."), usageHint: "mediumFeature"}}`\nImage.url may be `beeos-file://<fileId>`; the renderer resolves it. Do not convert it to a signed URL.\nusageHint: "icon" | "avatar" | "smallFeature" | "mediumFeature" | "largeFeature" | "header"\nfit (optional): "contain" | "cover" | "fill" | "none" | "scale-down"\n\n**Icon** — Material icon.\n`{Icon: {name: sv("check")}}`\n\n**Video** — Embed a video.\n`{Video: {url: sv("https://youtube.com/...")}}`\n\n**AudioPlayer** — Audio playback.\n`{AudioPlayer: {url: sv("https://..."), description: sv("Episode 1")}}`\n\n**Row** — Horizontal layout. Children referenced by ID.\n`{Row: {children: {explicitList: ["child1", "child2"]}, distribution: "spaceBetween"}}`\ndistribution: "start" | "center" | "end" | "spaceBetween" | "spaceAround" | "spaceEvenly"\nalignment: "start" | "center" | "end" | "stretch"\n\n**Column** — Vertical layout. Children referenced by ID.\n`{Column: {children: {explicitList: ["child1", "child2"]}}}`\nSame distribution/alignment options as Row.\n\n**List** — Vertical or horizontal list.\n`{List: {children: {explicitList: ["item1", "item2"]}, direction: "vertical"}}`\n\n**Card** — Container with a single child.\n`{Card: {child: "contentColumnId"}}`\n\n**Tabs** — Tabbed container.\n`{Tabs: {tabItems: [{title: sv("Tab 1"), child: "panel1"}, {title: sv("Tab 2"), child: "panel2"}]}}`\n\n**Divider** — Horizontal or vertical separator.\n`{Divider: {axis: "horizontal"}}`\n\n**Modal** — Dialog with trigger.\n`{Modal: {entryPointChild: "triggerBtnId", contentChild: "dialogContentId"}}`\n\n**Button** — Clickable with action.\n`{Button: {child: "labelTextId", action: {name: "onClick", context: [{key: "id", value: {literalString: "item-1"}}]}}}`\n\n**TextField** — Text input with data binding.\n`{TextField: {label: sv("Name"), text: {path: "/name"}, type: "shortText"}}`\ntype: "shortText" | "number" | "date" | "longText"\n\n**CheckBox** — Boolean toggle.\n`{CheckBox: {label: sv("Agree"), value: {path: "/agree"}}}`\n\n**DateTimeInput** — Date/time picker.\n`{DateTimeInput: {value: {path: "/date"}, enableDate: true, enableTime: false}}`\n\n**MultipleChoice** — Selection from options.\n`{MultipleChoice: {selections: {path: "/country"}, options: [{label: sv("US"), value: "US"}, {label: sv("CN"), value: "CN"}]}}`\n\n**Slider** — Numeric range input.\n`{Slider: {value: {path: "/volume"}, minValue: 0, maxValue: 100}}`\n\n**Chart** (BeeOS extension) — Data visualization.\n`{Chart: {chartType: sv("bar"), title: sv("Sales"), labels: ["Q1","Q2","Q3"], datasets: [{label: "Revenue", data: [100,200,150]}]}}`\nchartType: bar, horizontal_bar, line, area, scatter, pie, doughnut, radar, gauge, candlestick, funnel, sparkline\ngauge uses value + max instead of labels/datasets.\ncandlestick data: [{open,high,low,close}] (full field names, NOT o/h/l/c).\n\n### Composition\n\nNest by id: Column/Row `children.explicitList`, Card `child`. Example:\n`{"id":"s1","root":"r","components":[{"id":"t","component":{"Text":{"text":{"literalString":"Hello"},"usageHint":"h2"}}},{"id":"r","component":{"Column":{"children":{"explicitList":["t"]}}}}]}`\nTables: Card > Column > Row > Text. Forms: TextField/CheckBox + Button `action.context` paths.\n\n### render_form\nUse `render_form` for schema-driven forms when a domain schema exists.\nParameters: `domain`, `operation`, `data`, `entityId`.\n\n### close_ui\nCloses a UI surface. Use when user moves to a new topic or explicitly dismisses.\n';export const CANVAS_TERM_MAPPING='[System Note] Always use `render_ui` (not the canvas tool) for visual/structured content. Each component in the array MUST be: {"id":"x","component":{"TypeName":{props}}} — the "component" wrapper key is required. render_ui uses Google A2UI v0.8 format: flat component array with id references. Components: Text (usageHint: h1-h5/caption/body), Image, Icon, Video, AudioPlayer, Row (horizontal layout, children.explicitList), Column (vertical layout, children.explicitList), List, Card (single child), Tabs (tabItems), Divider, Modal, Button (child + action), TextField, CheckBox, MultipleChoice, Slider, DateTimeInput, Chart (BeeOS extension: chartType, labels, datasets). StringValue: {"literalString":"text"} or {"path":"/binding"}. root param = top-level component ID. Tables: Card > Column > Row > Text. Forms: TextField/CheckBox/Slider + Button with action.context paths. Chart: chartType(bar/line/pie/area/scatter/doughnut/radar/funnel/gauge/candlestick/sparkline), labels, datasets:[{label,data,color?}]. Candlestick: [{open,high,low,close}]. Gauge: value+max. Keep chat brief when canvas is used. Use `close_ui` to dismiss. Reuse same id to update; new id for new content.';export const CANVAS_UPDATE_HINT="[Canvas Rule] Same id in render_ui = update existing surface. Different id = new surface. When a surface is referenced for editing, you MUST reuse its exact id and provide the full updated component tree.";const MAX_CONTEXT_SURFACES=8,MAX_CONTEXT_CHARS=500;export function buildActiveSurfacesContext(e,t){let n;if(!t)return null;if(n=e.getAllActive(t),0===n.length)return null;const a=[...n].sort((e,t)=>t.updatedAt-e.updatedAt).slice(0,8),o=n.length-a.length;let i=`[Canvas State] Active surfaces: ${a.map(t=>{const n=e.summarize(t.surfaceId);if(!n)return`"${t.surfaceId}"`;const a=n.types.slice(0,3).join(", "),o=n.title?`: "${n.title}"`:"";return`"${t.surfaceId}" (${a}${o})`}).join("; ")}.`;return o>0&&(i+=` ...and ${o} more.`),i+=" To modify existing content, reuse its id in render_ui.",i.length>500&&(i=i.slice(0,497)+"..."),i}
@@ -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{writeRuntimeFailure}from"./runtime/write-runtime-failure.js";import{maybeRenameImConversationTitle}from"./runtime/conversation-title.js";import{logChatLatency}from"./chat-latency.js";const normalizePositiveTimeoutMs=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,PRE_RUN_LOOP_PROBE_MS=100,PRE_RUN_LOOP_LAG_LOG_MS=500,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,r)=>`${e}_${createHash("sha256").update(`${n}:${t}:${r}`).digest("hex").slice(0,32)}`;export const dispatchImInboundPrompt=async e=>{const{runtime:n,prompt:t,client:r,logger:a,promptRunStore:i,assistantMessageCommitRouter:s,toolResultObservationRouter:o,modelCallObservationRouter:c,sessionMutationCoordinator:d,abortTransport:l,activeRunAuthority:u}=e;if(!hasRuntimeReplyApi(n))return a.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 m=t.requestId??t.inboundMessageId,p=randomUUID(),I=Date.now();logChatLatency(a,"claw.inbound.received",m,I,{origin:e.origin??"im",request_id_source:t.requestId?"realtime_correlation":"message_id_fallback"});const g=e.origin??"im",h={protocol:"im",requestId:m,sessionId:t.sessionKey??"agent:main",sourceMessageId:t.inboundMessageId,chatId:t.channelId,origin:g},y=async(n,r)=>{if(e.onTerminalResult)try{await e.onTerminalResult(n,r)}catch(e){a.warn?.(`[im] terminal business callback failed requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}},_=promptRunKey(t.channelId,t.inboundMessageId),R=t.sessionKey??"agent:main",f=new AbortController;let b,E,w;const M=new Promise((e,n)=>{w=n});let $;const S=()=>{void 0!==$&&(clearTimeout($),$=void 0)};if(e.waitIfExclusive)try{await e.waitIfExclusive("im_dispatch",f.signal)}catch(n){return a.warn?.(`[im] runtime dispatch aborted waiting for exclusive mutation requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:n instanceof Error?n.message:String(n),cause:"abort"}),!1}if(!i.register(_,{requestId:m,channelId:t.channelId,requestCancel:async e=>{if(E??=e,f.abort(e),!b)return{ok:!0};const n=await d.requestCancel(b,l);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}},startedAt:I,sessionKey:R}))return a.warn?.(`[im] runtime dispatch rejected duplicate inboundMessageId=${t.inboundMessageId} requestId=${m} channel=${t.channelId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"duplicate_inbound_message_id",cause:"abort"}),!1;const v=deterministicReplyMessageId("asst",t.channelId,t.inboundMessageId,t.selfPrincipalId),C={conversationId:t.channelId,clientMessageId:v,idempotencyKey:v,replyTo:e.inReplyTo??t.inboundMessageId,requestId:m,type:"agent_reply",content:{}},q=r.messages.startStream(C);let k,O=!1;try{const e=await q.opened();O="duplicate"===e.outcome,logChatLatency(a,"claw.reply_stream.opened",m,I,{outcome:e.outcome})}catch(n){let s="unconfirmed";try{s=(await r.messages.getEnvelope(t.channelId,v)).state}catch{}return a.warn?.(`[im] runtime reply stream open outcome unknown requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} reply_id=${v} 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(_),!1}if(O)return a.info?.(`[im] runtime dispatch skipped existing deterministic reply requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"idempotent_reply_exists",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(_),!1;const L=new SerializedCallbackQueue,A=createImReplySink({stream:q,abortSignal:f.signal,callbackQueue:L,logger:a,requestId:m,startedAtMs:I,runId:p,channelId:t.channelId,inboundMessageId:t.inboundMessageId,origin:g,onAgentRunStart:e=>{d.bindRun(b,e),void 0!==k&&logChatLatency(a,"claw.runtime.pre_run_completed",m,k)}}),T=interceptAgentRuntimeFailures(A,"im_runtime",e=>a.warn?.(`[im] runtime failure reply suppressed requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} error_code=${e.errorCode}`)),D=T.sink;let F=!1;const U=async e=>{D.dispatcher.markComplete(e);try{await D.dispatcher.waitForIdle()}catch{}},N=()=>{S();const n=normalizePositiveTimeoutMs(e.abortTerminalGraceMs)??1e4;$=setTimeout(()=>{$=void 0,(async()=>{if(q.isTerminated)return;const e=f.signal.reason??E,n="string"==typeof e&&e.startsWith("chat_cancel");if(await U("failure"),!q.isTerminated)try{n?await q.cancel({stopReason:"user_stop"}):await failMessageStream(q,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}),a.warn?.(`[im] abort settlement watchdog terminalized reply requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} user_cancel=${n}`)}catch(e){a.warn?.(`[im] abort settlement watchdog terminal write failed requestId=${m} channel=${t.channelId}: ${formatErrMessage(e)}`)}})()},n)};f.signal.aborted?N():f.signal.addEventListener("abort",N,{once:!0});const K=()=>{if(!f.signal.aborted)return;const e=f.signal.reason;throw e instanceof Error?e:new Error(String(e??"runtime dispatch aborted"))};let P,G,Q,j,Y,x;const B=async e=>{if(!b)throw new Error("session_mutation_not_acquired");const n=await d.converge(b,e);return n.ok?(P=n.permit,!0):(a.error?.(`[im] session mutation recovery required requestId=${m} sessionKey=${R} reason=${n.recovery.reason}`),G={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{K();const l=d.tryStartInteractive({sessionKey:R,label:"im_chat_message"},"message_service");if(!l.ok){try{await failMessageStream(q,{body:SESSION_BUSY_PUBLIC_MESSAGE,stopReason:"error"})}catch(e){a.warn?.(`[im] session busy terminal write failed channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return a.warn?.(`[im] session write gate busy sessionKey=${R} channel=${t.channelId} in_reply_to=${t.inboundMessageId} active_operation=${l.activeOperationId??"unknown"}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"session_busy",cause:"runtime_error"}),!1}if(b=l.token,logChatLatency(a,"claw.session_gate.acquired",m,I),d.bindRun(b,p),j=s.register(R,A),Y=o.register(R,A),x=c.register(p,{requestId:m,startedAtMs:I,logger:a}),u){const e=d.snapshot(R);Q={runtimeConversationUid:t.channelId,runtimeSessionKey:t.channelId,historyGeneration:u.historyGeneration(t.channelId),targetMessageId:t.inboundMessageId,runtimeRunId:p,activeRunRegistrationId:`im:${e?.operationId??p}`},u.activeRuns.register(Q,async()=>{const e=`chat_cancel:activeRun:${Q.activeRunRegistrationId}`,n=await i.abort(_,e);return n.ok||"already_aborted"===n.reason?"accepted":"terminal_committing"===n.reason||"not_found"===n.reason?"already_terminal":"recovery_required"})}K(),a.info?.(`[im] runtime dispatching requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} origin=${g}`),e.onLifecycleEvent?.({type:"dispatch_start",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId}),k=Date.now(),logChatLatency(a,"claw.runtime.invoke",m,I);const f=setTimeout(()=>{const e=Date.now()-k-100;e>=500&&logChatLatency(a,"claw.runtime.event_loop_lag",m,k,{lag_ms:e,phase:"pre_run"})},100);f.unref?.();const E=dispatchRuntimeReplyWithRoute({runtime:n,pluginConfig:e.pluginConfig,preparedPrompt:t.dispatchInput,replyRoute:h,replySinks:{im:D},onDispatchInvoked:()=>{F=!0,logChatLatency(a,"claw.runtime.dispatch_returned",m,k)}}),w=await Promise.race([E,M]).finally(()=>{}),$=T.detectedFailure();if($)throw new Error($.diagnosticMessage);if(K(),await U("success"),K(),!i.claimTerminal(_,"completion")){if(K(),"cancelling"===i.phase(_))throw new Error("cancellation in progress");throw new Error("terminal outcome already claimed")}if(!await B("completed"))return await failMessageStream(q,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:"runtime_error",...G?{recovery:G}:{}}),!1;const S=D.dispatcher.getTerminalErrorText?.();if(void 0!==S){let n=!1;const r=classifyAgentRuntimeFailure({raw:S,source:"im_runtime",runtimeKind:"openclaw"});try{await writeRuntimeFailure(q,{body:r.publicMessage,stopReason:messageStopReasonForRuntimeFailure(r),businessErrorCode:r.businessErrorCode})}catch(e){n=!0,a.warn?.(`[im] runtime reply stream error-terminal write outcome unknown requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return u&&Q&&u.activeRuns.markTerminal(Q,"failed"),a.warn?.(`[im] runtime dispatch ended with an error terminal requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(w.counts)} terminalWriteFailed=${n} elapsed_ms=${Date.now()-I}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"runtime_error_terminal_reply",cause:"runtime_error"}),n||await y("",S),!1}let v=!1;try{await q.finalize({stopReason:"end_turn"})}catch(e){v=!0,a.warn?.(`[im] runtime reply stream finalize outcome unknown requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}u&&Q&&u.activeRuns.markTerminal(Q,"completed");const C={...w.counts,source:A.getSourceCount()};a.info?.(`[im] runtime dispatch completed requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(C)} elapsed_ms=${Date.now()-I}`),logChatLatency(a,"claw.turn.terminal",m,I,{outcome:v?"outcome_unknown":"completed",tool_count:C.tool}),e.onLifecycleEvent?.({type:"dispatch_complete",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,counts:C,...v?{terminalWriteFailed:!0}:{}});const O=D.dispatcher.getAccumulatedBody?.()??"";v||await y(O,"");const L="string"==typeof t.dispatchInput.message?t.dispatchInput.message:"";return maybeRenameImConversationTitle({client:r,conversationId:t.channelId,question:L,answer:O,logger:a}),!0}catch(n){await U("failure");const r=f.signal.aborted,s=r?f.signal.reason:void 0,o="cancelling"===i.phase(_),c=s??E,d="string"==typeof c&&c.startsWith("chat_cancel");let l="runtime_error";(r||o)&&(l="abort");const p=o||d||r?"cancellation":"completion";if(!i.claimTerminal(_,p)){if(a.warn?.(`[im] terminal claim raced requestId=${m} channel=${t.channelId}; best-effort envelope terminal if still streaming`),!q.isTerminated)try{d?await q.cancel({stopReason:"user_stop"}):await failMessageStream(q,{body:formatErrMessage(n),stopReason:"error"})}catch(e){a.warn?.(`[im] raced terminal write failed requestId=${m}: ${formatErrMessage(e)}`)}throw n}const g=d?"cancelled":"error";if(u&&Q&&u.activeRuns.markTerminal(Q,"cancelled"===g?"cancelled":"failed"),b&&!await B(g))throw d?await q.cancel({stopReason:"user_stop"}).catch(()=>{}):await failMessageStream(q,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:l,...G?{recovery:G}:{}}),n;const h=classifyAgentRuntimeFailure({raw:n,source:"im_runtime",runtimeKind:"openclaw",requestAborted:d}),R=d?h:T.detectedFailure()??h;let w=!1;try{d?await q.cancel({stopReason:"user_stop"}):await writeRuntimeFailure(q,{body:R.publicMessage,stopReason:messageStopReasonForRuntimeFailure(R),businessErrorCode:R.businessErrorCode})}catch(e){w=!0,a.warn?.(`[im] runtime stream terminal write failed requestId=${m} channel=${t.channelId}: ${formatErrMessage(e)}`)}throw a.warn?.(`[im] runtime dispatch failed requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} cause=${l} dispatchInvoked=${F} error_code=${R.errorCode}`),logChatLatency(a,"claw.turn.terminal",m,I,{outcome:l,error_code:R.errorCode}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:R.errorCode,cause:l}),d||w||await y("",R.publicMessage),n}finally{S(),x?.(),Y?.(),j?.(),i.unregister(_),F?P&&d.release(P):d.releaseBeforeRuntime(b)}};
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{writeRuntimeFailure}from"./runtime/write-runtime-failure.js";import{maybeRenameImConversationTitle}from"./runtime/conversation-title.js";import{logChatLatency}from"./chat-latency.js";const normalizePositiveTimeoutMs=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,PRE_RUN_LOOP_PROBE_MS=100,PRE_RUN_LOOP_LAG_LOG_MS=500,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,r)=>`${e}_${createHash("sha256").update(`${n}:${t}:${r}`).digest("hex").slice(0,32)}`;export const dispatchImInboundPrompt=async e=>{const{runtime:n,prompt:t,client:r,logger:a,promptRunStore:i,assistantMessageCommitRouter:s,toolResultObservationRouter:o,modelCallObservationRouter:c,sessionMutationCoordinator:d,abortTransport:l,activeRunAuthority:u}=e;if(!hasRuntimeReplyApi(n))return a.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 m=t.requestId??t.inboundMessageId,p=randomUUID(),I=Date.now();logChatLatency(a,"claw.inbound.received",m,I,{origin:e.origin??"im",request_id_source:t.requestId?"realtime_correlation":"message_id_fallback"});const g=e.origin??"im",h={protocol:"im",requestId:m,sessionId:t.sessionKey??"agent:main",sourceMessageId:t.inboundMessageId,chatId:t.channelId,origin:g},y=async(n,r)=>{if(e.onTerminalResult)try{await e.onTerminalResult(n,r)}catch(e){a.warn?.(`[im] terminal business callback failed requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}},_=promptRunKey(t.channelId,t.inboundMessageId),R=t.sessionKey??"agent:main",f=new AbortController;let b,E,w;const M=new Promise((e,n)=>{w=n});let $;const S=()=>{void 0!==$&&(clearTimeout($),$=void 0)};if(e.waitIfExclusive)try{await e.waitIfExclusive("im_dispatch",f.signal)}catch(n){return a.warn?.(`[im] runtime dispatch aborted waiting for exclusive mutation requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:n instanceof Error?n.message:String(n),cause:"abort"}),!1}if(!i.register(_,{requestId:m,channelId:t.channelId,requestCancel:async e=>{if(E??=e,f.abort(e),!b)return{ok:!0};const n=await d.requestCancel(b,l);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}},startedAt:I,sessionKey:R}))return a.warn?.(`[im] runtime dispatch rejected duplicate inboundMessageId=${t.inboundMessageId} requestId=${m} channel=${t.channelId}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"duplicate_inbound_message_id",cause:"abort"}),!1;const v=deterministicReplyMessageId("asst",t.channelId,t.inboundMessageId,t.selfPrincipalId),C={conversationId:t.channelId,clientMessageId:v,idempotencyKey:v,replyTo:e.inReplyTo??t.inboundMessageId,requestId:m,type:"agent_reply",content:{}},q=r.messages.startStream(C);let k,O=!1;try{const e=await q.opened();O="duplicate"===e.outcome,logChatLatency(a,"claw.reply_stream.opened",m,I,{outcome:e.outcome})}catch(n){let s="unconfirmed";try{s=(await r.messages.getEnvelope(t.channelId,v)).state}catch{}return a.warn?.(`[im] runtime reply stream open outcome unknown requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} reply_id=${v} 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(_),!1}if(O)return a.info?.(`[im] runtime dispatch skipped existing deterministic reply requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId}`),e.onLifecycleEvent?.({type:"skipped",reason:"idempotent_reply_exists",channelId:t.channelId,inboundMessageId:t.inboundMessageId}),i.unregister(_),!1;const L=new SerializedCallbackQueue,A=createImReplySink({stream:q,abortSignal:f.signal,callbackQueue:L,logger:a,requestId:m,startedAtMs:I,runId:p,channelId:t.channelId,inboundMessageId:t.inboundMessageId,origin:g,sessionKey:R,onAgentRunStart:e=>{d.bindRun(b,e),void 0!==k&&logChatLatency(a,"claw.runtime.pre_run_completed",m,k)}}),T=interceptAgentRuntimeFailures(A,"im_runtime",e=>a.warn?.(`[im] runtime failure reply suppressed requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} error_code=${e.errorCode}`)),D=T.sink;let F=!1;const U=async e=>{D.dispatcher.markComplete(e);try{await D.dispatcher.waitForIdle()}catch{}},K=()=>{S();const n=normalizePositiveTimeoutMs(e.abortTerminalGraceMs)??1e4;$=setTimeout(()=>{$=void 0,(async()=>{if(q.isTerminated)return;const e=f.signal.reason??E,n="string"==typeof e&&e.startsWith("chat_cancel");if(await U("failure"),!q.isTerminated)try{n?await q.cancel({stopReason:"user_stop"}):await failMessageStream(q,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}),a.warn?.(`[im] abort settlement watchdog terminalized reply requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} user_cancel=${n}`)}catch(e){a.warn?.(`[im] abort settlement watchdog terminal write failed requestId=${m} channel=${t.channelId}: ${formatErrMessage(e)}`)}})()},n)};f.signal.aborted?K():f.signal.addEventListener("abort",K,{once:!0});const N=()=>{if(!f.signal.aborted)return;const e=f.signal.reason;throw e instanceof Error?e:new Error(String(e??"runtime dispatch aborted"))};let P,G,Q,j,Y,x;const B=async e=>{if(!b)throw new Error("session_mutation_not_acquired");const n=await d.converge(b,e);return n.ok?(P=n.permit,!0):(a.error?.(`[im] session mutation recovery required requestId=${m} sessionKey=${R} reason=${n.recovery.reason}`),G={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{N();const l=d.tryStartInteractive({sessionKey:R,label:"im_chat_message"},"message_service");if(!l.ok){try{await failMessageStream(q,{body:SESSION_BUSY_PUBLIC_MESSAGE,stopReason:"error"})}catch(e){a.warn?.(`[im] session busy terminal write failed channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return a.warn?.(`[im] session write gate busy sessionKey=${R} channel=${t.channelId} in_reply_to=${t.inboundMessageId} active_operation=${l.activeOperationId??"unknown"}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"session_busy",cause:"runtime_error"}),!1}if(b=l.token,logChatLatency(a,"claw.session_gate.acquired",m,I),d.bindRun(b,p),j=s.register(R,A),Y=o.register(R,A),x=c.register(p,{requestId:m,startedAtMs:I,logger:a}),u){const e=d.snapshot(R);Q={runtimeConversationUid:t.channelId,runtimeSessionKey:t.channelId,historyGeneration:u.historyGeneration(t.channelId),targetMessageId:t.inboundMessageId,runtimeRunId:p,activeRunRegistrationId:`im:${e?.operationId??p}`},u.activeRuns.register(Q,async()=>{const e=`chat_cancel:activeRun:${Q.activeRunRegistrationId}`,n=await i.abort(_,e);return n.ok||"already_aborted"===n.reason?"accepted":"terminal_committing"===n.reason||"not_found"===n.reason?"already_terminal":"recovery_required"})}N(),a.info?.(`[im] runtime dispatching requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} origin=${g}`),e.onLifecycleEvent?.({type:"dispatch_start",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId}),k=Date.now(),logChatLatency(a,"claw.runtime.invoke",m,I);const f=setTimeout(()=>{const e=Date.now()-k-100;e>=500&&logChatLatency(a,"claw.runtime.event_loop_lag",m,k,{lag_ms:e,phase:"pre_run"})},100);f.unref?.();const E=dispatchRuntimeReplyWithRoute({runtime:n,pluginConfig:e.pluginConfig,preparedPrompt:t.dispatchInput,replyRoute:h,replySinks:{im:D},onDispatchInvoked:()=>{F=!0,logChatLatency(a,"claw.runtime.dispatch_returned",m,k)}}),w=await Promise.race([E,M]).finally(()=>{}),$=T.detectedFailure();if($)throw new Error($.diagnosticMessage);if(N(),await U("success"),N(),!i.claimTerminal(_,"completion")){if(N(),"cancelling"===i.phase(_))throw new Error("cancellation in progress");throw new Error("terminal outcome already claimed")}if(!await B("completed"))return await failMessageStream(q,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:"runtime_error",...G?{recovery:G}:{}}),!1;const S=D.dispatcher.getTerminalErrorText?.();if(void 0!==S){let n=!1;const r=classifyAgentRuntimeFailure({raw:S,source:"im_runtime",runtimeKind:"openclaw"});try{await writeRuntimeFailure(q,{body:r.publicMessage,stopReason:messageStopReasonForRuntimeFailure(r),businessErrorCode:r.businessErrorCode})}catch(e){n=!0,a.warn?.(`[im] runtime reply stream error-terminal write outcome unknown requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}return u&&Q&&u.activeRuns.markTerminal(Q,"failed"),a.warn?.(`[im] runtime dispatch ended with an error terminal requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(w.counts)} terminalWriteFailed=${n} elapsed_ms=${Date.now()-I}`),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:"runtime_error_terminal_reply",cause:"runtime_error"}),n||await y("",S),!1}let v=!1;try{await q.finalize({stopReason:"end_turn"})}catch(e){v=!0,a.warn?.(`[im] runtime reply stream finalize outcome unknown requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId}: ${formatErrMessage(e)}`)}u&&Q&&u.activeRuns.markTerminal(Q,"completed");const C={...w.counts,source:A.getSourceCount()};a.info?.(`[im] runtime dispatch completed requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} counts=${JSON.stringify(C)} elapsed_ms=${Date.now()-I}`),logChatLatency(a,"claw.turn.terminal",m,I,{outcome:v?"outcome_unknown":"completed",tool_count:C.tool}),e.onLifecycleEvent?.({type:"dispatch_complete",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,counts:C,...v?{terminalWriteFailed:!0}:{}});const O=D.dispatcher.getAccumulatedBody?.()??"";v||await y(O,"");const L="string"==typeof t.dispatchInput.message?t.dispatchInput.message:"";return maybeRenameImConversationTitle({client:r,conversationId:t.channelId,question:L,answer:O,logger:a}),!0}catch(n){await U("failure");const r=f.signal.aborted,s=r?f.signal.reason:void 0,o="cancelling"===i.phase(_),c=s??E,d="string"==typeof c&&c.startsWith("chat_cancel");let l="runtime_error";(r||o)&&(l="abort");const p=o||d||r?"cancellation":"completion";if(!i.claimTerminal(_,p)){if(a.warn?.(`[im] terminal claim raced requestId=${m} channel=${t.channelId}; best-effort envelope terminal if still streaming`),!q.isTerminated)try{d?await q.cancel({stopReason:"user_stop"}):await failMessageStream(q,{body:formatErrMessage(n),stopReason:"error"})}catch(e){a.warn?.(`[im] raced terminal write failed requestId=${m}: ${formatErrMessage(e)}`)}throw n}const g=d?"cancelled":"error";if(u&&Q&&u.activeRuns.markTerminal(Q,"cancelled"===g?"cancelled":"failed"),b&&!await B(g))throw d?await q.cancel({stopReason:"user_stop"}).catch(()=>{}):await failMessageStream(q,{body:SESSION_RECOVERY_REQUIRED_MESSAGE,stopReason:"error"}).catch(()=>{}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:SESSION_RECOVERY_REQUIRED_CODE,cause:l,...G?{recovery:G}:{}}),n;const h=classifyAgentRuntimeFailure({raw:n,source:"im_runtime",runtimeKind:"openclaw",requestAborted:d}),R=d?h:T.detectedFailure()??h;let w=!1;try{d?await q.cancel({stopReason:"user_stop"}):await writeRuntimeFailure(q,{body:R.publicMessage,stopReason:messageStopReasonForRuntimeFailure(R),businessErrorCode:R.businessErrorCode})}catch(e){w=!0,a.warn?.(`[im] runtime stream terminal write failed requestId=${m} channel=${t.channelId}: ${formatErrMessage(e)}`)}throw a.warn?.(`[im] runtime dispatch failed requestId=${m} channel=${t.channelId} in_reply_to=${t.inboundMessageId} cause=${l} dispatchInvoked=${F} error_code=${R.errorCode}`),logChatLatency(a,"claw.turn.terminal",m,I,{outcome:l,error_code:R.errorCode}),e.onLifecycleEvent?.({type:"dispatch_error",requestId:m,channelId:t.channelId,inboundMessageId:t.inboundMessageId,error:R.errorCode,cause:l}),d||w||await y("",R.publicMessage),n}finally{S(),x?.(),Y?.(),j?.(),i.unregister(_),F?P&&d.release(P):d.releaseBeforeRuntime(b)}};
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{createMcpNativeProjectionNudge,fingerprintMcpServers}from"./acp-gateway/mcp-native-projection-nudge.js";import{createImHotPathDeferral}from"./runtime/im-hot-path-deferral.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{MSConnectionStatusSnapshot}from"./runtime/ms-connection-status.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{GatewayClient}from"./gateway-client.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{marksFullRuntimeRegistered,planPluginRegistration,replayCachedPluginTools,resolveCapabilityToolReplay}from"./plugin-registration-plan.js";import{buildAppendSystemContext,buildToolRoutingHint}from"./prompt-system-context.js";import{CONNECTOR_MCP_PROXY_PATH,handleConnectorMcpRequest}from"./mcp-connector-proxy.js";import{CanvasStateManager}from"./canvas/state.js";import{CanvasRelayClient}from"./canvas/relay-client.js";import{createCanvasRelayService}from"./canvas/relay-service.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_PRESENTATION_GATE,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{OpenClawNativeMcpProjection}from"./acp-gateway/openclaw-native-mcp-projection.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";import{AgentMappingRegistry}from"./runtime/agent-mapping-registry.js";import{OpenClawRuntimeProcessOwner}from"./runtime/openclaw-runtime-process-owner.js";import{AgentGatewayHttpProjectionTransport,AgentGatewayTemplateOriginReporter,AgentProjectionProduction,OpenClawAgentProjectionSource}from"./runtime/agent-projection-production.js";import{MessageServiceRunOutputChannel}from"./runtime/message-service-run-output-channel.js";import{AssistantMessageCommitRouter}from"./runtime/assistant-message-commit-router.js";import{ToolResultObservationRouter}from"./runtime/tool-result-observation-router.js";import{ModelCallObservationRouter,registerModelCallObservationHooks}from"./runtime/model-call-observability.js";import{registerArouterLatencyRouting}from"./runtime/arouter-latency-routing.js";import{createPeerRunOutputRouterFactory}from"./runtime/run-output-composition.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null,sharedCanvasState=null,sharedAcpServer=null;const sharedSessionWriteGate=new SessionWriteGate,sharedPromptRunStore=new PromptRunStore;let lastOpenclawConfig;const sharedMcpNativeProjectionNudge=createMcpNativeProjectionNudge({getActiveImRunCount:()=>sharedPromptRunStore.size(),nudge:()=>{sharedAcpServer?.nudgeMcpNativeProjection()}}),sharedImHotPathDeferral=createImHotPathDeferral({getActiveImRunCount:()=>sharedPromptRunStore.size()});sharedPromptRunStore.setOnBecameIdle(()=>{sharedMcpNativeProjectionNudge.flushIfIdle({fingerprint:fingerprintMcpServers(lastOpenclawConfig)}),sharedImHotPathDeferral.notifyIdle()});const sharedAssistantMessageCommitRouter=new AssistantMessageCommitRouter,sharedToolResultObservationRouter=new ToolResultObservationRouter,sharedModelCallObservationRouter=new ModelCallObservationRouter;let sharedSessionMutationCoordinator,sharedRuntimeAuthority;const sharedMSConnectionStatus=new MSConnectionStatusSnapshot,sharedAgentMappings=new AgentMappingRegistry;let sharedGatewayClient,sharedRuntimeOwner,sharedAgentProjection,sharedRuntimeAgentCapabilities,sharedMcpProjection,sharedMcpCredentialVault;const sharedMessageClientOwner=new BeeosClawMessageClientProcessOwner;let sharedActiveMessageConsumer,sharedInboundExecutionJournal;const sharedTerminalManager=new TerminalManager;let _initializing=!1,_pluginRegisteredAt=0,_fullRuntimeRegistered=!1;const cachedCapabilityTools=[];let rawRegisterTool,wrappedRegisterTool;const ensureRegisterToolCache=e=>{rawRegisterTool||(rawRegisterTool=e.registerTool.bind(e),wrappedRegisterTool=e=>(cachedCapabilityTools.push(e),rawRegisterTool(e)),e.registerTool=wrappedRegisterTool)},ED25519_PKCS8_PREFIX=Buffer.from("302e020100300506032b657004220420","hex");function agentKeyPairToIdentity(e){return{publicKeyBase64:()=>Buffer.from(e.publicKey).toString("base64"),sign:async t=>{const{createPrivateKey:r,sign:o}=await import("node:crypto"),n=r({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(t),n))}}}let sharedPluginContext=null,unhandledRejectionObserverInstalled=!1;function installUnhandledRejectionObserver(e,t,r){unhandledRejectionObserverInstalled||(unhandledRejectionObserverInstalled=!0,process.on("unhandledRejection",r=>{const o=r instanceof Error?r.message:String(r),n=r instanceof Error?r.stack:void 0;e.logger.error?.(`[beeos-claw] unhandledRejection: ${o}${n?`\n${n}`:""}`),t({component:"plugin",domain:"fatal",name:"unhandled_rejection",severity:"error",summary:o,error:{code:"unhandled_rejection",message:o,...n?{stack:n}:{}}})}),process.on("uncaughtException",r=>{const o=r instanceof Error?r.message:String(r);e.logger.error?.(`[beeos-claw] uncaughtException: ${r instanceof Error?r.stack??r.message:String(r)}`),t({component:"plugin",domain:"fatal",name:"uncaught_exception",severity:"error",summary:o,error:{code:"uncaught_exception",message:o}})}))}function initializePlugin(e){let t,r;try{t=e.runtime?.config?.loadConfig?.()}catch{}try{r=resolveConfigWithMeta({pluginConfig:e.pluginConfig??{},openclawConfig:t})}catch(t){return e.logger.error?.(`[beeos-claw] Failed to resolve config: ${t instanceof Error?t.message:String(t)}`),null}const o=r.config,n=buildThirdPartyChannelSet(t);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:r.sources,openclawConfigPath:r.openclawConfigPath}});for(const t of r.validation)a({component:"plugin",domain:"config",name:"config.validation",severity:t.severity,summary:t.message,error:{code:t.code,message:t.message,nextSteps:t.nextSteps}}),"error"===t.severity?e.logger.error?.(`[beeos-claw] ${t.message}`):e.logger.warn?.(`[beeos-claw] ${t.message}`);if(r.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 t=Buffer.from(agentKeys.publicKey).toString("base64");e.logger.info?.(`[beeos-claw] v${PLUGIN_VERSION} registering (publicKey: ${t})`)}catch(t){e.logger.warn?.(`[beeos-claw] Failed to load agent key pair: ${t}`),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")),lastOpenclawConfig=t,{cfg:o,writeObs:a,thirdPartyChannels:n,openclawConfig:t}}function registerUploadTools(e,t,r){const{cfg:o,writeObs:n}=t;cleanupUploadToolResultCache().catch(t=>{e.logger.warn?.(`[beeos_upload_file] cache cleanup failed on startup: ${String(t)}`)}),e.registerTool({name:BEEOS_UPLOAD_TOOL_NAME,description:BEEOS_UPLOAD_TOOL_DESCRIPTION,parameters:BEEOS_UPLOAD_TOOL_SCHEMA,async execute(t,r,a){e.logger.debug?.(`[beeos_upload_file] toolCallId=${t} params=${JSON.stringify(r)}`);const i=await executeUpload(r,o,agentKeys,a);return e.logger.debug?.(`[beeos_upload_file] toolCallId=${t} ok=${i.ok}`),n({component:"upload-tool",domain:"tool",name:i.ok?"upload.success":"upload.failure",severity:i.ok?"info":"warn",payload:{toolCallId:t,ok:i.ok,fileCount:i.files?.length??0,error:i.error}}),(async r=>{try{await writeUploadToolResult(t,BEEOS_UPLOAD_TOOL_NAME,r),await cleanupUploadToolResultCache()}catch(r){e.logger.warn?.(`[beeos_upload_file] persist cache failed toolCallId=${t}: ${String(r)}`)}return r})(i)}}),e.registerTool({name:BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME,description:BEEOS_DESKTOP_SCREENSHOT_TOOL_DESCRIPTION,parameters:BEEOS_DESKTOP_SCREENSHOT_TOOL_SCHEMA,async execute(t,r,a){e.logger.debug?.(`[beeos_desktop_screenshot] toolCallId=${t} params=${JSON.stringify(r)}`);const i=await executeDesktopScreenshot(r,o,agentKeys,a);return e.logger.debug?.(`[beeos_desktop_screenshot] toolCallId=${t} 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:t,ok:i.ok,path:i.path,fileCount:i.files?.length??0,error:i.error}}),(async r=>{try{await writeUploadToolResult(t,BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME,r),await cleanupUploadToolResultCache()}catch(r){e.logger.warn?.(`[beeos_desktop_screenshot] persist cache failed toolCallId=${t}: ${String(r)}`)}return r})(i)}}),e.registerHttpRoute({path:"/beeos/files/stage",auth:"gateway",async handler(e,r){await handleFileStage(e,r,t.cfg)}}),e.registerHttpRoute({path:"/beeos/status",auth:"plugin",handler(e,t){handleStatusRequest(e,t,{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(t,r,a){e.logger.debug?.(`[beeos_list_files] toolCallId=${t} params=${JSON.stringify(r)}`);const i=await executeListFiles(r,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:t,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(t,r,a){e.logger.debug?.(`[beeos_fetch_file] toolCallId=${t} params=${JSON.stringify(r)}`);const i=await executeFetchFile(r,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:t,ok:i.ok,path:i.path,size:i.size,error:i.error}}),i}})}function registerConnectorMcpProxy(e,t){e.registerHttpRoute({path:CONNECTOR_MCP_PROXY_PATH,auth:"plugin",async handler(r,o){await handleConnectorMcpRequest(r,o,{agentGatewayUrl:t.cfg.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger})}})}function registerServices(e,t,r){const{cfg:o,writeObs:n,thirdPartyChannels:a}=t;if("function"!=typeof e.on){if(o.messageBus.enabled)throw new Error("openclaw_before_message_write_hook_unavailable")}else registerModelCallObservationHooks((t,r)=>e.on(t,r),sharedModelCallObservationRouter),e.on("before_message_write",(t,r)=>{const o=t.sessionKey??r.sessionKey;if(!o)return void e.logger.debug?.("[before_message_write] ignored assistant write without sessionKey");sharedAssistantMessageCommitRouter.route(o,t.message)||e.logger.debug?.(`[before_message_write] no active IM commit target sessionKey=${o} active_targets=${sharedAssistantMessageCommitRouter.size()}`)}),e.on("after_tool_call",(t,r)=>{const o=r.sessionKey,n=t.toolCallId??r.toolCallId,a=t.toolName||r.toolName;if(!o||!n||!a)return void e.logger.debug?.(`[after_tool_call] ignored structured result without correlation sessionKey=${o??"missing"} toolCallId=${n??"missing"}`);sharedToolResultObservationRouter.route(o,{toolCallId:n,toolName:a,rawResult:t.result,isError:Boolean(t.error)})||e.logger.debug?.(`[after_tool_call] no active IM observation target sessionKey=${o} active_targets=${sharedToolResultObservationRouter.size()}`)});const i=agentKeys?(e,t)=>agentFetch(e,agentKeys,t):void 0,s=agentKeys?(e,t)=>agentBoundFetch(e,agentKeys,t):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"));if(sharedMcpProjection??=new OpenClawNativeMcpProjection,!sharedMcpCredentialVault&&agentKeys&&l&&(sharedMcpCredentialVault=new LocalEncryptedCredentialReferenceVault(join(c,"mcp-credential-vault.json"),l,agentKeys.privateKey)),!sharedRuntimeAuthority&&agentKeys&&o.agentGatewayUrl&&i&&s&&(sharedRuntimeAuthority=new RuntimeProductionAuthority({stateDirectory:c,agentGatewayUrl:o.agentGatewayUrl,agentFetch:i,boundAgentFetch:s,keys:agentKeys,logger:e.logger,getMSConnectionStatus:e=>sharedMSConnectionStatus.get(e)})),sharedGatewayClient??=new GatewayClient({url:o.gateway.url,token:o.gateway.token||"",clientId:o.gateway.clientId,openclawCanvasCompat:!!o.openclawCanvasCompat,retry:{baseMs:o.retry.baseMs,maxMs:o.retry.maxMs,maxAttempts:o.retry.maxAttempts}}),sharedRuntimeAgentCapabilities??={canvas:o.canvasEnabled,terminal:!1,webSsh:!1,fileSystem:!0,image:!0,audio:!1,mcp:!0,cron:!0,streaming:!0,pushNotifications:o.messageBus.enabled},!sharedAgentProjection&&sharedRuntimeAuthority&&i&&o.agentGatewayUrl){const t=sharedRuntimeAuthority;sharedAgentProjection=new AgentProjectionProduction({authority:t,stateFile:join(t.stateDirectory,"runtime-agent-projection.json"),journal:t.journal,identities:t.identities,source:new OpenClawAgentProjectionSource(()=>sharedGatewayClient),transport:new AgentGatewayHttpProjectionTransport({agentGatewayUrl:o.agentGatewayUrl,boundAgentFetch:t.boundAgentFetch,getRuntimeLeaseCredential:e=>{const r=t.authorityLease;if(!r||r.instanceId!==e.instanceId||r.leaseId!==e.leaseId||r.runtimeEpoch!==e.runtimeEpoch||r.handlerIdentity!==e.handlerIdentity)throw new Error("agent projection fenced by current runtime lease");return r.runtimeLeaseCredential},onAccepted:t=>{0!==t.size&&sharedAgentMappings.replace(t).catch(t=>{e.logger.warn?.("[agent-projection] mapping publication failed: "+(t instanceof Error?t.message:String(t)))})}}),capabilities:sharedRuntimeAgentCapabilities,logger:e.logger})}sharedRuntimeOwner??=new OpenClawRuntimeProcessOwner(sharedGatewayClient,sharedAgentProjection,e.logger);const d=sharedRuntimeOwner;sharedSessionMutationCoordinator??=new SessionMutationCoordinator(sharedSessionWriteGate,new OpenClawSessionWriteQuiescence(e.logger,{agentId:o.gateway.agentId||"main"}),e.logger,{onRecoveryRequired:t=>{scheduleProcessRuntimeRestart(t,e.logger)}});const u=sharedSessionMutationCoordinator;if(!sharedAcpServer){const t=sharedRuntimeAuthority&&o.agentGatewayUrl?new AgentGatewayTemplateOriginReporter({agentGatewayUrl:o.agentGatewayUrl,boundAgentFetch:sharedRuntimeAuthority.boundAgentFetch}):void 0;sharedAcpServer=new ACPServer(o,e.logger,u,n,i,l,a,sharedTerminalManager,sharedRuntimeAuthority,void 0,sharedMcpProjection,sharedMcpCredentialVault,s,{gateway:d.client,agentProjection:sharedAgentProjection,markAgentProjectionDirty:()=>sharedAgentProjection?.markDirty(),templateOriginReporter:t,agentMappings:sharedAgentMappings,agentCapabilities:sharedRuntimeAgentCapabilities,createRunOutputRouter:createPeerRunOutputRouterFactory(new MessageServiceRunOutputChannel({getClient:()=>sharedMessageClientOwner.current?.client??null,logger:e.logger,writeObs:n})),runExclusive:(t,r,o)=>sharedImHotPathDeferral.runExclusive(t,r,e.logger,o)})}const m=sharedAcpServer,p=()=>sharedAgentMappings.snapshot()?.values().next().value;r.current=m,sharedMcpNativeProjectionNudge.request({registrationMode:e.registrationMode,fingerprint:fingerprintMcpServers(t.openclawConfig),logger:e.logger});const y=sharedPromptRunStore,{enabled:h,principalId:f,a2aEnabled:w}=o.messageBus;let C;o.agentGatewayUrl&&agentKeys&&(C=createTokenProvider({agentGatewayUrl:o.agentGatewayUrl,identity:agentKeyPairToIdentity(agentKeys)}));const R=process.env.MESSAGE_RUNTIME_DELIVERY_KEY?.trim(),v=f||process.env.BEEOS_PRINCIPAL_ID||"",b=C?sharedMessageClientOwner.configure({tokenProvider:C,identityId:v,...o.messageBus.serviceUrl?{serviceUrl:o.messageBus.serviceUrl}:{},logger:e.logger,...sharedRuntimeAuthority&&R?{runtimeDelivery:{scopedDeliveryKey:R,authority:{currentLease:()=>{const e=sharedRuntimeAuthority?.authorityLease,t=sharedRuntimeAuthority?.currentConsumerBinding();return e&&t?{...e,journalStoreId:t.journalStoreId,journalGeneration:t.journalGeneration}:null}}}}:{}}):void 0,_=sharedRuntimeAuthority,S=o.messageBus.serviceUrl;if(b&&_&&S&&R){const e=b.nodeTransport;m.setCronConversationDelivery({deliver:async t=>{const r=_.authorityLease;if(!r)throw new Error("current runtime authority lease unavailable for cron delivery");const o=new MessageServiceCronDeliveryHttpAuthority({messageServiceUrl:S,fetchAuthorized:async(t,o)=>await e.fetchRuntimeChannel(t,{runtimeLeaseCredential:r.runtimeLeaseCredential},o)});return await o.deliver({...t,runtimeLeaseCredential:r.runtimeLeaseCredential})}})}else m.setCronConversationDelivery(void 0);const E=sharedRuntimeAuthority?.journal??new FileRuntimeCommandJournal(join(c,"runtime-command-journal.json"),void 0,void 0,{onMaintenanceError:t=>e.logger.error?.(`[runtime-journal] event=checkpoint_failed trigger=threshold error=${t instanceof Error?t.message:String(t)}`)}),O=d.createRegistrationToken();e.registerService({id:"beeos-acp",async start(){const t=Date.now(),r=_pluginRegisteredAt?t-_pluginRegisteredAt:-1;e.logger.info?.(`[beeos-claw] beeos-acp service start() begin, sincePluginRegister=${r}ms, bridge.url=${o.bridge.url||"(empty)"}`);const n=await createRuntimeServiceProductionCandidate({...o.agentGatewayUrl&&i?{authorizationAdapter:new AgentGatewayGrantJwksReadinessAdapter({agentGatewayUrl:o.agentGatewayUrl})}:{},...b&&o.agentGatewayUrl&&s&&R?{transportAdapter:new RuntimeServiceTransportReadinessAdapter({agentGatewayUrl:o.agentGatewayUrl,messageService:b.nodeTransport,messageClient:b.client,configuredServiceUrl:o.messageBus.serviceUrl,boundAgentFetch:s,scopedDeliveryKey:R})}:{},commandJournal:E,poisonJournal:new FileRuntimePoisonDeliveryJournal(join(c,"runtime-poison-deliveries.json")),onConsumerDeliveryError:t=>{const r=t instanceof Error?`${t.name}: ${t.message}${"status"in t?` status=${t.status}`:""}${"code"in t?` code=${t.code}`:""}\n${t.stack??""}`:String(t);e.logger.error?.(`[runtime-service] delivery worker failed before acknowledgement; delivery retained for retry: ${r}`)},onConsumerLeaseStarvation:({starvedForMs:t,recovered:r})=>{const o=Math.round(t/1e3);r?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:t=>e.logger.error?.(`[runtime-service] request failed operationId=${t.operationId} method=${t.method}: ${t.error instanceof Error?t.error.stack??t.error.message:String(t.error)}`)});if(sharedRuntimeAuthority?.setRuntimeServiceMethods("ready"===n.status?n.serviceMethods:[]),sharedRuntimeAuthority?.setRuntimeResourceBindingContractRevision("ready"===n.status?n.resourceBindingContractRevision:void 0),m.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 t=Date.now(),r=await discoverBridgeFromGW(o.agentGatewayUrl,i);if(e.logger.info?.(`[beeos-claw] bridge discovery took ${Date.now()-t}ms result=${r?.url??"null"}`),r)o.bridge.url=r.url,writeDiscoveryCache({bridge:{url:r.url,discoveredAt:Date.now()}}),e.logger.info?.(`[beeos-claw] Bridge URL discovered from Agent Gateway: ${r.url}`);else{const t=readDiscoveryCache();t?.bridge?.url?(o.bridge.url=t.bridge.url,e.logger.info?.(`[beeos-claw] Bridge URL from cache (GW unreachable): ${t.bridge.url}`)):e.logger.debug?.("[beeos-claw] Bridge URL not available (no GW, no cache) — bridge disabled")}}await d.acquire(O);try{await m.start(o.bridge.url||void 0)}catch(e){throw await d.release(O),e}e.logger.info?.(`[beeos-claw] ACP Server started (total service start: ${Date.now()-t}ms)`)},async stop(){try{await d.release(O)}finally{await m.stop()}await E.checkpoint(),e.logger.info?.("[runtime-service] command journal checkpoint completed"),e.logger.info?.("[beeos-claw] ACP Server stopped")}});if(h&&void 0!==C&&C){const t=sharedMessageClientOwner.createRegistrationToken(),r=async(e,t)=>{const r=await C(e);if(!r.identityId||r.identityId!==e)throw new Error("Message SDK token identity did not match the current runtime authority");return{principalId:e,runtimeEpoch:t}},a=(e,t,r)=>({getLocalAgentId:()=>t,resolveLocalAgentId:e=>e===r?t:void 0,getDefaultPlatformAgent:()=>({instanceId:e.principalId}),ensureImInboundSession:(e,t,r,o)=>{m.ensureImInboundSession(e,t,r,o)},chatAbort:(e,t)=>m.chatAbort(e,t)}),i=sharedRuntimeAuthority?new RuntimeAuthorityMessageBusLifecycle({authority:sharedRuntimeAuthority,logger:e.logger,bus:{connect:async i=>{await sharedMessageClientOwner.acquire(t,async t=>{const s=await r(i.instanceId,i.runtimeEpoch),l=t.client,c={leaseId:i.leaseId,runtimeEpoch:i.runtimeEpoch};sharedMSConnectionStatus.bind(c);const d=new Map,p=new MessageProjectionInboundConsumer({client:l,bindings:()=>{const t=sharedRuntimeAuthority?.authorityLease;if(!t||t.instanceId!==s.principalId||t.runtimeEpoch!==s.runtimeEpoch)return[];const r=sharedAgentMappings.snapshot();return r?[...r.entries()].flatMap(([r,i])=>{if(!i.id||i.instanceId!==s.principalId)return[];let c=d.get(i.id);return c&&c.localAgentId===r||(c={localAgentId:r,inbound:createImInboundHandler({runtime:e.runtime,pluginConfig:e.pluginConfig,client:l,localServer:a(s,r,i.id),promptRunStore:y,waitIfExclusive:(t,r)=>sharedImHotPathDeferral.waitIfExclusive(t,e.logger,r).then(()=>{}),assistantMessageCommitRouter:sharedAssistantMessageCommitRouter,toolResultObservationRouter:sharedToolResultObservationRouter,modelCallObservationRouter:sharedModelCallObservationRouter,sessionMutationCoordinator:u,logger:e.logger,writeObs:n,principalIdOverride:s.principalId,activeRunAuthority:m.getActiveRunAuthority(),...o.agentGatewayUrl&&agentKeys?{completeA2ATask:(t,r,n)=>completeA2ATask({agentGatewayUrl:o.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},t,r,n)}:{}})},d.set(i.id,c)),[{principalId:s.principalId,targetAgentId:i.id,runtimeEpoch:t.runtimeEpoch,inbound:c.inbound}]}):[]},executionJournal:g,logger:e.logger}),h=sharedAgentMappings.subscribe(()=>{sharedActiveMessageConsumer===p&&p.refresh()}),f=sharedRuntimeAuthority?.subscribeAuthority(()=>{sharedActiveMessageConsumer===p&&p.refresh()})??(()=>{}),w=new MessageClientTerminalStateRecovery({logger:e.logger,isCurrent:()=>{if(sharedActiveMessageConsumer!==p)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 m.rebindRuntimeDelivery()}catch(t){e.logger.warn?.(`[beeos-claw] runtime Service delivery rebind failed after MessageClient recovery: ${t instanceof Error?t.message:String(t)}`)}}}});let C=l.getSnapshot().connection;sharedMSConnectionStatus.setIfCurrent(c,C);const R=l.subscribe(()=>{const t=l.getSnapshot();t.connection!==C&&(C=t.connection,sharedMSConnectionStatus.setIfCurrent(c,t.connection),"connected"===t.connection?(w.notifyConnected(),e.logger.info?.(`[beeos-claw] connection transport=message_service_realtime state=connected localAgentId=${m.getLocalAgentId()} principalId=${s.principalId}`)):"failed"===t.connection?(e.logger.warn?.(`[beeos-claw] connection transport=message_service_realtime state=failed principalId=${s.principalId} recoveryError=${t.recoveryError??"unknown"}`),w.notifyFailed()):"disconnected"!==t.connection&&"reconnecting"!==t.connection||e.logger.warn?.(`[beeos-claw] connection transport=message_service_realtime state=${t.connection} principalId=${s.principalId}; SDK owns reconnect/recovery`))});sharedActiveMessageConsumer=p,p.start();try{await l.connect()}catch(e){throw sharedActiveMessageConsumer===p&&(sharedActiveMessageConsumer=void 0),sharedMSConnectionStatus.resetIfCurrent(c),w.dispose(),await p.stop(),h(),f(),R(),await l.disconnect().catch(()=>{}),e}try{await m.rebindRuntimeDelivery()}catch(t){e.logger.warn?.(`[beeos-claw] runtime Service delivery rebind failed after MessageClient connect: ${t instanceof Error?t.message:String(t)}`)}return async()=>{sharedActiveMessageConsumer===p&&(sharedActiveMessageConsumer=void 0),sharedMSConnectionStatus.resetIfCurrent(c),w.dispose(),await p.stop(),f(),R(),await l.disconnect(),e.logger.info?.("[beeos-claw] MessageClient disconnected")}})},disconnect:async()=>{await sharedMessageClientOwner.release(t)}}}):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 h?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=${h})`);if(!1!==w&&o.agentGatewayUrl){const t=e=>m.getGatewaySessionKeyForToolCall(e),r=e=>m.getMentionsForToolCall(e),n=(e,t)=>{m.broadcastNotification(e,t)},a=new TaskHandleRegistry;registerA2ATools(e,{agentGatewayUrl:o.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},a,t,r,n,()=>p()?.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 t=sharedCanvasState;if(o.agentGatewayUrl&&agentKeys){let r;t.setLogger(e.logger);{const e=o.agentGatewayUrl,t=agentKeys;r=async()=>{const r=await agentFetch(`${e}/api/v1/canvas/token`,t,{method:"POST"});if(!r.ok){const e=await r.text().catch(()=>"");throw new Error(`Canvas token returned ${r.status}: ${e}`)}return r.json()}}const n=n=>{const a=new CanvasRelayClient({canvasRelayUrl:o.canvasRelayUrl,agentKeys:agentKeys,agentId:n.id,displayName:n.displayName,avatarUrl:n.avatarUrl,tokenProvider:r,onConnected:()=>m.flushPendingCanvasLinks(),onReconnect:()=>m.onRelayReconnected(),logger:e.logger});t.setRelayClient(a),t.setAgentId(n.id),m.wireRelayActionHandler(),a.connect(),e.logger.info?.(`[beeos-claw] Canvas Relay multiplexed client connected (agentId=${n.id}, name=${n.name??"none"})`)};e.registerService(createCanvasRelayService({getAgent:p,onceAgentReady:e=>sharedAgentMappings.onceReady(e),initRelay:n,stopRelay:()=>t.getRelayClient()?.disconnect(),logger:e.logger})),e.logger.info?.("[beeos-claw] Canvas Relay configured")}else e.logger.warn?.("[beeos-claw] Canvas Relay unavailable: agentGatewayUrl or agentKeys missing");const r={stateManager:t,logger:e.logger,writeObs:e=>n(e),isCanvasActive:e=>!!e&&m.isCanvasActiveForGatewayKey(e),getActiveSessionId:e=>m.getSessionIdForToolCall(e),getPlatformAgentId:e=>m.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>m.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>sharedAgentMappings.waitForSynchronization()},i=new FormSchemaRegistry,s=buildCronSchemas(a);i.register(s.create),i.register(s.update);const l=new ResourceDescriptorRegistry;l.register(CRON_RESOURCE);const c=e=>async(t,r,o)=>(m.preMapToolCallSession(t),e(t,r));e.registerTool({name:RENDER_UI_TOOL_NAME,description:RENDER_UI_TOOL_DESCRIPTION,parameters:RENDER_UI_TOOL_SCHEMA,execute:c(createRenderUiHandler(r))}),e.registerTool({name:CLOSE_UI_TOOL_NAME,description:CLOSE_UI_TOOL_DESCRIPTION,parameters:CLOSE_UI_TOOL_SCHEMA,execute:c(createCloseUiHandler(r))}),m.setCanvasState(t,{schemaRegistry:i,descriptorRegistry:l});const g=m.getFormBuilder();g&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(i),parameters:RENDER_FORM_TOOL_SCHEMA,execute:c(createRenderFormHandler({registry:i,builder:g,logger:e.logger}))});const d=m.getResourceRenderer();if(d){generateActionTools(l,e,async(e,t)=>m.gatewayRequest(e,t),d,e.logger)}const u=buildToolRoutingHint(BEEOS_FILE_RESOURCE_ROUTING_RULE,BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME),y=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),h=256,f=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,t)=>{if(t?.sessionKey){if(f.size>=h&&!f.has(t.sessionKey)){const e=f.keys().next().value;void 0!==e&&f.delete(e)}f.set(t.sessionKey,t.channelId)}const r=isThirdPartyChannel(a,{sessionKey:t?.sessionKey,channelId:t?.channelId});if(!t?.sessionKey&&!r)return{appendSystemContext:u};const o=Boolean(t?.sessionKey&&m.isCanvasActiveForGatewayKey(t.sessionKey));return{appendSystemContext:buildAppendSystemContext({thirdParty:r,canvasActive:o,canvasSystemPrompt:`${CANVAS_PRESENTATION_GATE}\n\n${CANVAS_SYSTEM_PROMPT}`,toolRoutingHint:u})}}),e.logger.debug?.("[beeos-claw] Canvas system prompt hook registered: before_prompt_build")}catch{e.logger.debug?.("[beeos-claw] before_prompt_build hook not supported")}try{e.on("before_tool_call",(e,t)=>{if(t?.sessionKey&&m.setPendingToolCallSessionKey(t.sessionKey),!y.has(e.toolName))return;const r=t?.sessionKey?f.get(t.sessionKey):void 0;return isThirdPartyChannel(a,{sessionKey:t?.sessionKey,channelId:r})?{block:!0,blockReason:"Canvas tools are not available in this channel. Respond with plain text only."}:t?.sessionKey?m.isCanvasActiveForGatewayKey(t.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 t=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(){t.start(),e.logger.info?.(`[beeos-claw] Terminal WS started on port ${o.terminalWsPort}`)},stop(){t.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(t.openclawConfig){const r=mirrorOpenClawConfig(t.openclawConfig);r.copied?e.logger.info?.(`[beeos-claw] mirrored openclaw config to ${r.destinationPath}`):e.logger.debug?.(`[beeos-claw] openclaw config mirror skipped: ${r.reason}`)}}const plugin={id:"beeos-claw",name:"beeos-claw",description:"BeeOS platform bridge plugin for OpenClaw. Provides file upload/download, ACP bridge, and observability.",version:PLUGIN_VERSION,register(e){if(_initializing)return;const t=planPluginRegistration(e.registrationMode,_fullRuntimeRegistered);if("skip"!==t){_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=(t,r)=>{registerUploadTools(e,t,r),registerConnectorMcpProxy(e,t),registerArouterLatencyRouting(e),registerArouterMediaProviders(e)};if("capabilities"===t&&sharedPluginContext){if(cachedCapabilityTools.length>0){const t=resolveCapabilityToolReplay({currentRegisterTool:e.registerTool,wrappedRegisterTool:wrappedRegisterTool,originalRegisterTool:rawRegisterTool}),r=replayCachedPluginTools(r=>t.call(e,r),cachedCapabilityTools);return registerArouterLatencyRouting(e),registerArouterMediaProviders(e),void e.logger.info?.(`[beeos-claw] capability re-register mode=${e.registrationMode} tools_only count=${r}`)}const t={current:sharedAcpServer??null};return r(sharedPluginContext,t),registerServices(e,sharedPluginContext,t),void e.logger.info?.(`[beeos-claw] capability re-register mode=${e.registrationMode} capabilities_only`)}const o=initializePlugin(e);if(!o)return;const n={current:null};installUnhandledRejectionObserver(e,o.writeObs,n),ensureRegisterToolCache(e),cachedCapabilityTools.length=0,r(o,n),registerServices(e,o,n),sharedPluginContext=o,marksFullRuntimeRegistered(e.registrationMode,t)&&(_fullRuntimeRegistered=!0),_pluginRegisteredAt=Date.now(),e.logger.info?.("[beeos-claw] Plugin registered successfully")}finally{_initializing=!1}}else e.logger.info?.(`[beeos-claw] skip re-register mode=${e.registrationMode??"full"} already_registered`)}};export default plugin;
1
+ import{fileDeliveryFinalizeDecision}from"./upload/finalize-hook.js";import{resolveConfigWithMeta,mirrorOpenClawConfig}from"./config.js";import{hasRuntimeReplyApi}from"./runtime/reply-router.js";import{PromptRunStore}from"./runtime/prompt-run-store.js";import{createMcpNativeProjectionNudge,fingerprintMcpServers}from"./acp-gateway/mcp-native-projection-nudge.js";import{createImHotPathDeferral}from"./runtime/im-hot-path-deferral.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{MSConnectionStatusSnapshot}from"./runtime/ms-connection-status.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{GatewayClient}from"./gateway-client.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{deliverableFileExtractor}from"./upload/deliverable-extractor.js";import{observeToolCallForDelivery,rememberDeliveryWorkspace}from"./upload/delivery-observe.js";import{BEEOS_FILE_RESOURCE_ROUTING_RULE}from"./files/beeos-file-uri.js";import{marksFullRuntimeRegistered,planPluginRegistration,replayCachedPluginTools,resolveCapabilityToolReplay}from"./plugin-registration-plan.js";import{buildAppendSystemContext,buildToolRoutingHint,canvasToolBlockReason}from"./prompt-system-context.js";import{CONNECTOR_MCP_PROXY_PATH,handleConnectorMcpRequest}from"./mcp-connector-proxy.js";import{CanvasStateManager}from"./canvas/state.js";import{CanvasRelayClient}from"./canvas/relay-client.js";import{createCanvasRelayService}from"./canvas/relay-service.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_PRESENTATION_GATE,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{OpenClawNativeMcpProjection}from"./acp-gateway/openclaw-native-mcp-projection.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";import{AgentMappingRegistry}from"./runtime/agent-mapping-registry.js";import{OpenClawRuntimeProcessOwner}from"./runtime/openclaw-runtime-process-owner.js";import{AgentGatewayHttpProjectionTransport,AgentGatewayTemplateOriginReporter,AgentProjectionProduction,OpenClawAgentProjectionSource}from"./runtime/agent-projection-production.js";import{MessageServiceRunOutputChannel}from"./runtime/message-service-run-output-channel.js";import{AssistantMessageCommitRouter}from"./runtime/assistant-message-commit-router.js";import{ToolResultObservationRouter}from"./runtime/tool-result-observation-router.js";import{ModelCallObservationRouter,registerModelCallObservationHooks}from"./runtime/model-call-observability.js";import{registerArouterLatencyRouting}from"./runtime/arouter-latency-routing.js";import{createPeerRunOutputRouterFactory}from"./runtime/run-output-composition.js";const PLUGIN_VERSION=resolvePluginVersion();let agentKeys=null,sharedCanvasState=null,sharedAcpServer=null;const sharedSessionWriteGate=new SessionWriteGate,sharedPromptRunStore=new PromptRunStore;let lastOpenclawConfig;const sharedMcpNativeProjectionNudge=createMcpNativeProjectionNudge({getActiveImRunCount:()=>sharedPromptRunStore.size(),nudge:()=>{sharedAcpServer?.nudgeMcpNativeProjection()}}),sharedImHotPathDeferral=createImHotPathDeferral({getActiveImRunCount:()=>sharedPromptRunStore.size()});sharedPromptRunStore.setOnBecameIdle(()=>{sharedMcpNativeProjectionNudge.flushIfIdle({fingerprint:fingerprintMcpServers(lastOpenclawConfig)}),sharedImHotPathDeferral.notifyIdle()});const sharedAssistantMessageCommitRouter=new AssistantMessageCommitRouter,sharedToolResultObservationRouter=new ToolResultObservationRouter,sharedModelCallObservationRouter=new ModelCallObservationRouter;let sharedSessionMutationCoordinator,sharedRuntimeAuthority;const sharedMSConnectionStatus=new MSConnectionStatusSnapshot,sharedAgentMappings=new AgentMappingRegistry;let sharedGatewayClient,sharedRuntimeOwner,sharedAgentProjection,sharedRuntimeAgentCapabilities,sharedMcpProjection,sharedMcpCredentialVault;const sharedMessageClientOwner=new BeeosClawMessageClientProcessOwner;let sharedActiveMessageConsumer,sharedInboundExecutionJournal;const sharedTerminalManager=new TerminalManager;let _initializing=!1,_pluginRegisteredAt=0,_fullRuntimeRegistered=!1;const cachedCapabilityTools=[];let rawRegisterTool,wrappedRegisterTool;const ensureRegisterToolCache=e=>{rawRegisterTool||(rawRegisterTool=e.registerTool.bind(e),wrappedRegisterTool=e=>(cachedCapabilityTools.push(e),rawRegisterTool(e)),e.registerTool=wrappedRegisterTool)},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 sharedPluginContext=null,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),registerLocalFileExtractor(deliverableFileExtractor),e.logger.info?.("[beeos-claw] Upload deps + local file extractors registered")),lastOpenclawConfig=r,{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 registerConnectorMcpProxy(e,r){e.registerHttpRoute({path:CONNECTOR_MCP_PROXY_PATH,auth:"plugin",async handler(t,o){await handleConnectorMcpRequest(t,o,{agentGatewayUrl:r.cfg.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger})}})}function registerServices(e,r,t){const{cfg:o,writeObs:n,thirdPartyChannels:a}=r;if("function"!=typeof e.on){if(o.messageBus.enabled)throw new Error("openclaw_before_message_write_hook_unavailable")}else registerModelCallObservationHooks((r,t)=>e.on(r,t),sharedModelCallObservationRouter),e.on("before_message_write",(r,t)=>{const o=r.sessionKey??t.sessionKey;if(!o)return void e.logger.debug?.("[before_message_write] ignored assistant write without sessionKey");sharedAssistantMessageCommitRouter.route(o,r.message)||e.logger.debug?.(`[before_message_write] no active IM commit target sessionKey=${o} active_targets=${sharedAssistantMessageCommitRouter.size()}`)}),e.on("after_tool_call",(r,t)=>{const o=t.sessionKey,n=r.toolCallId??t.toolCallId,a=r.toolName||t.toolName;if(!o||!n||!a)return void e.logger.debug?.(`[after_tool_call] ignored structured result without correlation sessionKey=${o??"missing"} toolCallId=${n??"missing"}`);observeToolCallForDelivery(r,o);sharedToolResultObservationRouter.route(o,{toolCallId:n,toolName:a,rawResult:r.result,isError:Boolean(r.error)})||e.logger.debug?.(`[after_tool_call] no active IM observation target sessionKey=${o} active_targets=${sharedToolResultObservationRouter.size()}`)});const 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"),g=sharedInboundExecutionJournal??=new FileInboundExecutionJournal(join(c,"inbound-execution-journal.v1.jsonl"));if(sharedMcpProjection??=new OpenClawNativeMcpProjection,!sharedMcpCredentialVault&&agentKeys&&l&&(sharedMcpCredentialVault=new LocalEncryptedCredentialReferenceVault(join(c,"mcp-credential-vault.json"),l,agentKeys.privateKey)),!sharedRuntimeAuthority&&agentKeys&&o.agentGatewayUrl&&i&&s&&(sharedRuntimeAuthority=new RuntimeProductionAuthority({stateDirectory:c,agentGatewayUrl:o.agentGatewayUrl,agentFetch:i,boundAgentFetch:s,keys:agentKeys,logger:e.logger,getMSConnectionStatus:e=>sharedMSConnectionStatus.get(e)})),sharedGatewayClient??=new GatewayClient({url:o.gateway.url,token:o.gateway.token||"",clientId:o.gateway.clientId,openclawCanvasCompat:!!o.openclawCanvasCompat,retry:{baseMs:o.retry.baseMs,maxMs:o.retry.maxMs,maxAttempts:o.retry.maxAttempts}}),sharedRuntimeAgentCapabilities??={canvas:o.canvasEnabled,terminal:!1,webSsh:!1,fileSystem:!0,image:!0,audio:!1,mcp:!0,cron:!0,streaming:!0,pushNotifications:o.messageBus.enabled},!sharedAgentProjection&&sharedRuntimeAuthority&&i&&o.agentGatewayUrl){const r=sharedRuntimeAuthority;sharedAgentProjection=new AgentProjectionProduction({authority:r,stateFile:join(r.stateDirectory,"runtime-agent-projection.json"),journal:r.journal,identities:r.identities,source:new OpenClawAgentProjectionSource(()=>sharedGatewayClient),transport:new AgentGatewayHttpProjectionTransport({agentGatewayUrl:o.agentGatewayUrl,boundAgentFetch:r.boundAgentFetch,getRuntimeLeaseCredential:e=>{const t=r.authorityLease;if(!t||t.instanceId!==e.instanceId||t.leaseId!==e.leaseId||t.runtimeEpoch!==e.runtimeEpoch||t.handlerIdentity!==e.handlerIdentity)throw new Error("agent projection fenced by current runtime lease");return t.runtimeLeaseCredential},onAccepted:r=>{0!==r.size&&sharedAgentMappings.replace(r).catch(r=>{e.logger.warn?.("[agent-projection] mapping publication failed: "+(r instanceof Error?r.message:String(r)))})}}),capabilities:sharedRuntimeAgentCapabilities,logger:e.logger})}sharedRuntimeOwner??=new OpenClawRuntimeProcessOwner(sharedGatewayClient,sharedAgentProjection,e.logger);const d=sharedRuntimeOwner;sharedSessionMutationCoordinator??=new SessionMutationCoordinator(sharedSessionWriteGate,new OpenClawSessionWriteQuiescence(e.logger,{agentId:o.gateway.agentId||"main"}),e.logger,{onRecoveryRequired:r=>{scheduleProcessRuntimeRestart(r,e.logger)}});const u=sharedSessionMutationCoordinator;if(!sharedAcpServer){const r=sharedRuntimeAuthority&&o.agentGatewayUrl?new AgentGatewayTemplateOriginReporter({agentGatewayUrl:o.agentGatewayUrl,boundAgentFetch:sharedRuntimeAuthority.boundAgentFetch}):void 0;sharedAcpServer=new ACPServer(o,e.logger,u,n,i,l,a,sharedTerminalManager,sharedRuntimeAuthority,void 0,sharedMcpProjection,sharedMcpCredentialVault,s,{gateway:d.client,agentProjection:sharedAgentProjection,markAgentProjectionDirty:()=>sharedAgentProjection?.markDirty(),templateOriginReporter:r,agentMappings:sharedAgentMappings,agentCapabilities:sharedRuntimeAgentCapabilities,createRunOutputRouter:createPeerRunOutputRouterFactory(new MessageServiceRunOutputChannel({getClient:()=>sharedMessageClientOwner.current?.client??null,logger:e.logger,writeObs:n})),runExclusive:(r,t,o)=>sharedImHotPathDeferral.runExclusive(r,t,e.logger,o)})}const m=sharedAcpServer,p=()=>sharedAgentMappings.snapshot()?.values().next().value;t.current=m,sharedMcpNativeProjectionNudge.request({registrationMode:e.registrationMode,fingerprint:fingerprintMcpServers(r.openclawConfig),logger:e.logger});const y=sharedPromptRunStore,{enabled:h,principalId:f,a2aEnabled:w}=o.messageBus;let v;o.agentGatewayUrl&&agentKeys&&(v=createTokenProvider({agentGatewayUrl:o.agentGatewayUrl,identity:agentKeyPairToIdentity(agentKeys)}));const C=process.env.MESSAGE_RUNTIME_DELIVERY_KEY?.trim(),R=f||process.env.BEEOS_PRINCIPAL_ID||"",b=v?sharedMessageClientOwner.configure({tokenProvider:v,identityId:R,...o.messageBus.serviceUrl?{serviceUrl:o.messageBus.serviceUrl}:{},logger:e.logger,...sharedRuntimeAuthority&&C?{runtimeDelivery:{scopedDeliveryKey:C,authority:{currentLease:()=>{const e=sharedRuntimeAuthority?.authorityLease,r=sharedRuntimeAuthority?.currentConsumerBinding();return e&&r?{...e,journalStoreId:r.journalStoreId,journalGeneration:r.journalGeneration}:null}}}}:{}}):void 0,_=sharedRuntimeAuthority,S=o.messageBus.serviceUrl;if(b&&_&&S&&C){const e=b.nodeTransport;m.setCronConversationDelivery({deliver:async r=>{const t=_.authorityLease;if(!t)throw new Error("current runtime authority lease unavailable for cron delivery");const o=new MessageServiceCronDeliveryHttpAuthority({messageServiceUrl:S,fetchAuthorized:async(r,o)=>await e.fetchRuntimeChannel(r,{runtimeLeaseCredential:t.runtimeLeaseCredential},o)});return await o.deliver({...r,runtimeLeaseCredential:t.runtimeLeaseCredential})}})}else m.setCronConversationDelivery(void 0);const E=sharedRuntimeAuthority?.journal??new FileRuntimeCommandJournal(join(c,"runtime-command-journal.json"),void 0,void 0,{onMaintenanceError:r=>e.logger.error?.(`[runtime-journal] event=checkpoint_failed trigger=threshold error=${r instanceof Error?r.message:String(r)}`)}),O=d.createRegistrationToken();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})}:{},...b&&o.agentGatewayUrl&&s&&C?{transportAdapter:new RuntimeServiceTransportReadinessAdapter({agentGatewayUrl:o.agentGatewayUrl,messageService:b.nodeTransport,messageClient:b.client,configuredServiceUrl:o.messageBus.serviceUrl,boundAgentFetch:s,scopedDeliveryKey:C})}:{},commandJournal:E,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),m.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 d.acquire(O);try{await m.start(o.bridge.url||void 0)}catch(e){throw await d.release(O),e}e.logger.info?.(`[beeos-claw] ACP Server started (total service start: ${Date.now()-r}ms)`)},async stop(){try{await d.release(O)}finally{await m.stop()}await E.checkpoint(),e.logger.info?.("[runtime-service] command journal checkpoint completed"),e.logger.info?.("[beeos-claw] ACP Server stopped")}});if(h&&void 0!==v&&v){const r=sharedMessageClientOwner.createRegistrationToken(),t=async(e,r)=>{const t=await v(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)=>{m.ensureImInboundSession(e,r,t,o)},chatAbort:(e,r)=>m.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,c={leaseId:i.leaseId,runtimeEpoch:i.runtimeEpoch};sharedMSConnectionStatus.bind(c);const d=new Map,p=new MessageProjectionInboundConsumer({client:l,bindings:()=>{const r=sharedRuntimeAuthority?.authorityLease;if(!r||r.instanceId!==s.principalId||r.runtimeEpoch!==s.runtimeEpoch)return[];const t=sharedAgentMappings.snapshot();return t?[...t.entries()].flatMap(([t,i])=>{if(!i.id||i.instanceId!==s.principalId)return[];let c=d.get(i.id);return c&&c.localAgentId===t||(c={localAgentId:t,inbound:createImInboundHandler({runtime:e.runtime,pluginConfig:e.pluginConfig,client:l,localServer:a(s,t,i.id),promptRunStore:y,waitIfExclusive:(r,t)=>sharedImHotPathDeferral.waitIfExclusive(r,e.logger,t).then(()=>{}),assistantMessageCommitRouter:sharedAssistantMessageCommitRouter,toolResultObservationRouter:sharedToolResultObservationRouter,modelCallObservationRouter:sharedModelCallObservationRouter,sessionMutationCoordinator:u,logger:e.logger,writeObs:n,principalIdOverride:s.principalId,activeRunAuthority:m.getActiveRunAuthority(),...o.agentGatewayUrl&&agentKeys?{completeA2ATask:(r,t,n)=>completeA2ATask({agentGatewayUrl:o.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},r,t,n)}:{}})},d.set(i.id,c)),[{principalId:s.principalId,targetAgentId:i.id,runtimeEpoch:r.runtimeEpoch,inbound:c.inbound}]}):[]},executionJournal:g,logger:e.logger}),h=sharedAgentMappings.subscribe(()=>{sharedActiveMessageConsumer===p&&p.refresh()}),f=sharedRuntimeAuthority?.subscribeAuthority(()=>{sharedActiveMessageConsumer===p&&p.refresh()})??(()=>{}),w=new MessageClientTerminalStateRecovery({logger:e.logger,isCurrent:()=>{if(sharedActiveMessageConsumer!==p)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 m.rebindRuntimeDelivery()}catch(r){e.logger.warn?.(`[beeos-claw] runtime Service delivery rebind failed after MessageClient recovery: ${r instanceof Error?r.message:String(r)}`)}}}});let v=l.getSnapshot().connection;sharedMSConnectionStatus.setIfCurrent(c,v);const C=l.subscribe(()=>{const r=l.getSnapshot();r.connection!==v&&(v=r.connection,sharedMSConnectionStatus.setIfCurrent(c,r.connection),"connected"===r.connection?(w.notifyConnected(),e.logger.info?.(`[beeos-claw] connection transport=message_service_realtime state=connected localAgentId=${m.getLocalAgentId()} principalId=${s.principalId}`)):"failed"===r.connection?(e.logger.warn?.(`[beeos-claw] connection transport=message_service_realtime state=failed principalId=${s.principalId} recoveryError=${r.recoveryError??"unknown"}`),w.notifyFailed()):"disconnected"!==r.connection&&"reconnecting"!==r.connection||e.logger.warn?.(`[beeos-claw] connection transport=message_service_realtime state=${r.connection} principalId=${s.principalId}; SDK owns reconnect/recovery`))});sharedActiveMessageConsumer=p,p.start();try{await l.connect()}catch(e){throw sharedActiveMessageConsumer===p&&(sharedActiveMessageConsumer=void 0),sharedMSConnectionStatus.resetIfCurrent(c),w.dispose(),await p.stop(),h(),f(),C(),await l.disconnect().catch(()=>{}),e}try{await m.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===p&&(sharedActiveMessageConsumer=void 0),sharedMSConnectionStatus.resetIfCurrent(c),w.dispose(),await p.stop(),f(),C(),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 h?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=${h})`);if(!1!==w&&o.agentGatewayUrl){const r=e=>m.getGatewaySessionKeyForToolCall(e),t=e=>m.getMentionsForToolCall(e),n=(e,r)=>{m.broadcastNotification(e,r)},a=new TaskHandleRegistry;registerA2ATools(e,{agentGatewayUrl:o.agentGatewayUrl,agentKeys:agentKeys,logger:e.logger},a,r,t,n,()=>p()?.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:()=>m.flushPendingCanvasLinks(),onReconnect:()=>m.onRelayReconnected(),logger:e.logger});r.setRelayClient(a),r.setAgentId(n.id),m.wireRelayActionHandler(),a.connect(),e.logger.info?.(`[beeos-claw] Canvas Relay multiplexed client connected (agentId=${n.id}, name=${n.name??"none"})`)};e.registerService(createCanvasRelayService({getAgent:p,onceAgentReady:e=>sharedAgentMappings.onceReady(e),initRelay:n,stopRelay:()=>r.getRelayClient()?.disconnect(),logger:e.logger})),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),getActiveSessionId:e=>m.getSessionIdForToolCall(e),getPlatformAgentId:e=>m.getPlatformAgentIdForToolCall(e),getGatewaySessionKey:e=>m.getGatewaySessionKeyForToolCall(e),waitForAgentSync:()=>sharedAgentMappings.waitForSynchronization()},i=new FormSchemaRegistry,s=buildCronSchemas(a);i.register(s.create),i.register(s.update);const l=new ResourceDescriptorRegistry;l.register(CRON_RESOURCE);const c=e=>async(r,t,o)=>(m.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))}),m.setCanvasState(r,{schemaRegistry:i,descriptorRegistry:l});const g=m.getFormBuilder();g&&e.registerTool({name:RENDER_FORM_TOOL_NAME,description:buildRenderFormDescription(i),parameters:RENDER_FORM_TOOL_SCHEMA,execute:c(createRenderFormHandler({registry:i,builder:g,logger:e.logger}))});const d=m.getResourceRenderer();if(d){generateActionTools(l,e,async(e,r)=>m.gatewayRequest(e,r),d,e.logger)}const u=buildToolRoutingHint(BEEOS_FILE_RESOURCE_ROUTING_RULE,BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME),y=new Set([RENDER_UI_TOOL_NAME,CLOSE_UI_TOOL_NAME,RENDER_FORM_TOOL_NAME]),h=256,f=new Map;if("function"==typeof e.on){try{e.on("before_prompt_build",(e,r)=>{if(r?.sessionKey){if(f.size>=h&&!f.has(r.sessionKey)){const e=f.keys().next().value;void 0!==e&&f.delete(e)}f.set(r.sessionKey,r.channelId),rememberDeliveryWorkspace(r.sessionKey,r.workspaceDir)}const t=isThirdPartyChannel(a,{sessionKey:r?.sessionKey,channelId:r?.channelId});return r?.sessionKey||t?{appendSystemContext:buildAppendSystemContext({thirdParty:t,canvasSystemPrompt:`${CANVAS_PRESENTATION_GATE}\n\n${CANVAS_SYSTEM_PROMPT}`,toolRoutingHint:u})}:{appendSystemContext:u}}),e.logger.debug?.("[beeos-claw] Canvas system prompt hook registered: before_prompt_build")}catch{e.logger.debug?.("[beeos-claw] before_prompt_build hook not supported")}try{e.on("before_tool_call",(e,r)=>{if(r?.sessionKey&&m.setPendingToolCallSessionKey(r.sessionKey),!y.has(e.toolName))return;const t=r?.sessionKey?f.get(r.sessionKey):void 0,o=isThirdPartyChannel(a,{sessionKey:r?.sessionKey,channelId:t}),n=canvasToolBlockReason({thirdParty:o,hasSession:Boolean(r?.sessionKey)});return n?{block:!0,blockReason:n}:void 0}),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 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)return;const r=planPluginRegistration(e.registrationMode,_fullRuntimeRegistered);if("skip"!==r){_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 t=(r,t)=>{registerUploadTools(e,r,t),registerConnectorMcpProxy(e,r),registerArouterLatencyRouting(e),registerArouterMediaProviders(e)};if("capabilities"===r&&sharedPluginContext){if(cachedCapabilityTools.length>0){const r=resolveCapabilityToolReplay({currentRegisterTool:e.registerTool,wrappedRegisterTool:wrappedRegisterTool,originalRegisterTool:rawRegisterTool}),t=replayCachedPluginTools(t=>r.call(e,t),cachedCapabilityTools);return registerArouterLatencyRouting(e),registerArouterMediaProviders(e),void e.logger.info?.(`[beeos-claw] capability re-register mode=${e.registrationMode} tools_only count=${t}`)}const r={current:sharedAcpServer??null};return t(sharedPluginContext,r),registerServices(e,sharedPluginContext,r),void e.logger.info?.(`[beeos-claw] capability re-register mode=${e.registrationMode} capabilities_only`)}const o=initializePlugin(e);if(!o)return;const n={current:null};installUnhandledRejectionObserver(e,o.writeObs,n),ensureRegisterToolCache(e),cachedCapabilityTools.length=0,t(o,n),agentKeys&&o.cfg.agentGatewayUrl&&(e.on?e.on("before_agent_finalize",fileDeliveryFinalizeDecision):e.logger.warn?.("[file-delivery] OpenClaw plugin hooks unavailable; finalization check disabled")),registerServices(e,o,n),sharedPluginContext=o,marksFullRuntimeRegistered(e.registrationMode,r)&&(_fullRuntimeRegistered=!0),_pluginRegisteredAt=Date.now(),e.logger.info?.("[beeos-claw] Plugin registered successfully")}finally{_initializing=!1}}else e.logger.info?.(`[beeos-claw] skip re-register mode=${e.registrationMode??"full"} already_registered`)}};export default plugin;
@@ -1 +1 @@
1
- export const buildToolRoutingHint=(e,t)=>`\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- ${t}: 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- ${e}\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 ${t}.`;const THIRD_PARTY_CONTEXT='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.',CANVAS_CLOSED_HINT="Canvas panel is not confirmed open. Prefer plain text. If the user explicitly asks to show, display, render, or visualize on canvas, you MAY call render_ui; the tool guard blocks it when the panel is closed. Do not read beeos-canvas/SKILL.md; call render_ui if needed. Do not call close_ui or render_form unless the user asks.";export const buildAppendSystemContext=e=>e.thirdParty?THIRD_PARTY_CONTEXT:e.canvasActive?`${e.canvasSystemPrompt}${e.toolRoutingHint}`:`${CANVAS_CLOSED_HINT}${e.toolRoutingHint}`;
1
+ export const buildToolRoutingHint=(e,t)=>`\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- ${t}: 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- ${e}\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 ${t}.`;const THIRD_PARTY_CONTEXT='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.';export const buildAppendSystemContext=e=>e.thirdParty?THIRD_PARTY_CONTEXT:`${e.canvasSystemPrompt}${e.toolRoutingHint}`;export function canvasToolBlockReason(e){return e.thirdParty?"Canvas tools are not available in this channel. Respond with plain text only.":e.hasSession?void 0:"Canvas session unknown. Respond with plain text instead."}
@@ -1 +1 @@
1
- import{toMessagePart}from"../message-client-types.js";import{isUploadArtifactToolName,resolveToolArtifactLinks}from"../upload/tool-artifact-enrichment.js";import{resolveRuntimeToolArguments,resolveRuntimeToolCallId,resolveRuntimeToolName}from"./tool-envelope-metadata.js";import{extractSearchSources,mergeSearchSource,searchSourceProvider}from"./search-source-extractor.js";import{logChatLatency}from"../chat-latency.js";const STREAMING_SNAPSHOT_PUBLISH_INTERVAL_MS=200,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 summarizeObservedToolError=e=>{if(null==e)return;if("string"==typeof e)return e.slice(0,180);if("object"!=typeof e)return String(e).slice(0,180);const t=e;for(const e of["error","message","blockReason","details","text"]){const r=t[e];if("string"==typeof r&&r.trim())return r.slice(0,180);if(isPlainRecord(r)&&"string"==typeof r.message&&r.message.trim())return r.message.slice(0,180)}try{const t=JSON.stringify(e);return t?t.slice(0,180):void 0}catch{return}};export function createImReplySink(e){const t=e.startedAtMs??Date.now();let r=!1;const n=new Map,s=new Set,o={text:""};let a="",i="",l=!1,d=!1;const u=new Set;let m;const c={receivedSnapshotCount:0,streamingPublishCount:0,terminalPublishCount:0},g=[];let p;const h=new Map,f=new Map,v=[],y=[],b=new Map,I=new Map;let _=0,S=1;const x=(e,t={})=>({schema_version:1,seq:e,...t}),$=new Set,q=new Set,w=t=>{t!==o.text&&(t.startsWith(o.text)?e.stream.appendBody(t.slice(o.text.length)):e.stream.setBody(t),o.text=t)},T=e=>{void 0===e.publishTimer&&(e.publishTimer=setTimeout(()=>{e.publishTimer=void 0,le("body_preview_flush",()=>{m===e&&(e=>{o.text!==e.targetBody&&(w(e.targetBody),e.streamingPublishCount+=1)})(e)})},200))},P=e=>{const t=m;t?(void 0!==t.publishTimer&&(clearTimeout(t.publishTimer),t.publishTimer=void 0),t.targetBody=e,o.text!==e&&(w(e),t.terminalPublishCount+=1),c.receivedSnapshotCount+=t.receivedSnapshotCount,c.streamingPublishCount+=t.streamingPublishCount,c.terminalPublishCount+=t.terminalPublishCount,m=void 0):w(e)},k=t=>{if(void 0!==t.partIndex)return;const r=_,n=S;e.stream.addPart(toMessagePart({type:"text",text:t.text,state:"done",_seq:n,data:x(n)})),t.partIndex=r,_+=1,S+=1},E=()=>{if(!d){d=!0;for(const e of g)k(e)}},C=e=>{const t={text:e};g.push(t),d&&k(t)},R=(t,r)=>{for(const n of t){const t=I.get(n.url);if(t){const r=mergeSearchSource(t,n);if(r.title===t.title&&r.snippet===t.snippet)continue;const s={...r,partIndex:t.partIndex,displaySeq:t.displaySeq,toolUseId:t.toolUseId};e.stream.replacePart(t.partIndex,toMessagePart({type:"source",url:s.url,...s.title?{title:s.title}:{},...s.snippet?{snippet:s.snippet}:{},tool_use_id:s.toolUseId,_seq:s.displaySeq,data:x(s.displaySeq)})),I.set(n.url,s);continue}const s={...n,partIndex:_,displaySeq:S,toolUseId:r};e.stream.addPart(toMessagePart({type:"source",url:n.url,...n.title?{title:n.title}:{},...n.snippet?{snippet:n.snippet}:{},tool_use_id:r,_seq:s.displaySeq,data:x(s.displaySeq)})),I.set(n.url,s),_+=1,S+=1,oe.source+=1}},M=(e,t,r,n)=>toMessagePart({type:"thinking",text:t,state:r,_seq:e.displaySeq,_started_at_ms:e.startedAtMs,...void 0!==n?{_duration_ms:n}:{},data:x(e.displaySeq,{started_at_ms:e.startedAtMs,...void 0!==n?{duration_ms:n}:{}})}),O=t=>{void 0===t.publishTimer&&(t.publishTimer=setTimeout(()=>{t.publishTimer=void 0,le("reasoning_snapshot_flush",()=>{p===t&&(t=>{t.publishedText!==t.text&&(e.stream.replacePart(t.partIndex,M(t,t.text,"streaming")),t.publishedText=t.text,t.streamingPublishCount+=1)})(t)})},200))},A=()=>{const r=p;if(!r)return;void 0!==r.publishTimer&&(clearTimeout(r.publishTimer),r.publishTimer=void 0);const n=Math.max(0,Math.round(performance.now()-r.startedAtMonotonicMs));e.stream.replacePart(r.partIndex,M(r,r.text,"done",n)),p=void 0,logChatLatency(e.logger,"claw.reasoning.settled",e.requestId,t,{reasoning_duration_ms:n,reasoning_chars:r.text.length,reasoning_snapshot_count:r.receivedSnapshotCount,reasoning_streaming_publish_count:r.streamingPublishCount,reasoning_coalesced_snapshot_count:Math.max(0,r.receivedSnapshotCount-r.streamingPublishCount),reasoning_publish_interval_ms:200})},N=e=>{const t=v.indexOf(e);t>=0&&v.splice(t,1)},U=e=>{const t=e?v.filter(t=>h.get(t)?.name===e):v;return 1===t.length?t[0]:void 0},j=e=>{const t=y.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 y.splice(n,1),r},L=(e,t)=>{if(f.set(e,t),e===t)return;const r=b.get(e);if(!r)return;const n=z(t);Q(n,r),b.delete(e);const s=h.get(t);s&&(X(s,n),b.delete(t))},B=e=>f.get(e)??e,F=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),z=e=>{let t=b.get(e);return t||(t={},b.set(e,t)),t},D=(e,t)=>{if(!e?.length&&!t?.length)return;const r=[],n=new Map;for(const s of[...e??[],...t??[]]){const e=n.get(s.url);void 0===e?(n.set(s.url,r.length),r.push(s)):r[e]=mergeSearchSource(r[e],s)}return r},Q=(e,t)=>{e.name??=t.name,e.arguments??=t.arguments,t.toolStartObserved&&(e.toolStartObserved=!0),t.itemEventObserved&&(e.itemEventObserved=!0),t.progress?.length&&(e.progress=[...e.progress??[],...t.progress]),e.sources=D(e.sources,t.sources)},J=(t,r,n)=>{const s="tool"===t.name&&"tool"!==r?r:t.name,o=!F(t.arguments)&&F(n)?n:t.arguments;s===t.name&&o===t.arguments||(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:s,arguments:o,state:t.state,_seq:t.displaySeq,data:x(t.displaySeq)})),t.name=s,t.arguments=o)},H=(t,r,n,s)=>{const o=h.get(t);if(o)return J(o,r,n),s&&!v.includes(t)&&"streaming"===o.state&&v.push(t),o;E();const a=b.get(t),i="tool"!==r?r:a?.name&&"tool"!==a.name?a.name:r,l=F(n)?n:a?.arguments??n,d=((t,r,n)=>{const s=_,o=S;return e.stream.addPart(toMessagePart({type:"tool_use",id:t,name:r,arguments:n,state:"streaming",_seq:o,data:x(o)})),_+=1,S+=1,{partIndex:s,displaySeq:o}})(t,i,l),u={id:t,name:i,arguments:l,partIndex:d.partIndex,displaySeq:d.displaySeq,state:"streaming",itemEventObserved:!0===a?.itemEventObserved,toolStartObserved:!0===a?.toolStartObserved};return h.set(t,u),(s||u.toolStartObserved||u.itemEventObserved)&&(v.includes(t)||"streaming"!==u.state||v.push(t)),a&&X(u,a),b.delete(t),u},W=(e,t)=>{const r=(e=>{const t=f.get(e);return t?h.get(t):h.get(e)})(e);if(r)return void 0===t.name&&void 0===t.arguments||J(r,t.name??r.name,t.arguments??r.arguments),t.toolStartObserved&&(r.toolStartObserved=!0),t.itemEventObserved&&(r.itemEventObserved=!0),(t.pending||t.toolStartObserved||t.itemEventObserved)&&!v.includes(r.id)&&"streaming"===r.state&&v.push(r.id),r;const n=z(e);void 0!==t.name&&(n.name=t.name),void 0!==t.arguments&&(n.arguments=t.arguments),t.toolStartObserved&&(n.toolStartObserved=!0),t.itemEventObserved&&(n.itemEventObserved=!0)},G=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.displaySeq,data:x(t.displaySeq)})),t.state="done")},V=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(V).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${V(t)}`).join(",")}}`},K=(t,r,n)=>{const s=`${n?"1":"0"}:${V(r)}`;if(t.progressSignature===s)return;const o=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=_;return e.stream.addPart(o),t.progressPartIndex=r,t.progressSignature=s,void(_+=1)}e.stream.replacePart(t.progressPartIndex,o),t.progressSignature=s},X=(e,t)=>{if(t.progress?.length){for(const r of t.progress)K(e,r.content,r.isError);G(e),N(e.id)}t.sources?.length&&R(t.sources,e.id)},Y=e=>[...new Set([e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e)).map(e=>e.trim()).filter(Boolean))],Z=(e,t=[])=>{const r=[...new Set([...Y(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.":""},ee=t=>{for(const r of t)e.stream.addPart(toMessagePart({type:"resource_link",uri:r.uri,name:r.name,...r.mimeType?{mimeType:r.mimeType}:{}})),_+=1},te=e=>{$.add(e),e.finally(()=>{$.delete(e)})},re=(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),s=(async()=>{let s=[];try{s=await resolveToolArtifactLinks({toolCallId:t.id,toolName:t.name,rawResult:n,rawText:"string"==typeof r.text?r.text:void 0,payloadMediaUrls:Y(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!==s.length?(e.logger.info?.(`[im-sink] tool artifact enrichment resolved ${s.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=s.map(e=>e.uri);K(t,Z(r,n),!0===r.isError),((t,r,n)=>{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),_+=1,ee(r))})(t,s,!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}`)});te(s)},ne=(e,t,r)=>{const n=f.get(e);if(n)return W(n,{name:t,arguments:r});const s=W(e,{name:t,arguments:r});if(s)return s;const o=U(t);if(o)return L(e,o),W(o,{name:t,arguments:r});W(e,{name:t,arguments:r})},se=(e,t)=>{const r=f.get(e);if(r)return W(r,{name:t,itemEventObserved:!0,pending:!0});const n=W(e,{name:t,itemEventObserved:!0,pending:!0});if(n)return n;const s=(e=>{const t=v.filter(t=>{const r=h.get(t);return!(void 0===r||e&&r.name!==e||r.itemEventObserved)});return 1===t.length?t[0]:void 0})(t);if(s)return L(e,s),W(s,{name:t,itemEventObserved:!0,pending:!0});const o=j(t);W(e,{name:t,arguments:o?.arguments,itemEventObserved:!0,pending:!0})},oe={tool:0,block:0,final:0,source:0};let ae,ie=!1;const le=(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):!ie||(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=>(le("tool_progress",()=>{const r=resolveRuntimeToolCallId(t),n=resolveRuntimeToolName(t),s=resolveRuntimeToolArguments(t),o=Z(t),a=!0===t.isError;let i;if(r){if(i=ne(r,n,s),!i){const e=z(r);return n&&(e.name=n),F(s)&&(e.arguments=s),e.progress=[...e.progress??[],{content:o,isError:a}],void(oe.tool+=1)}}else{const t=n?U(n):U();if(!t)return j(n),void e.logger.debug?.(`[im-sink] unattributed tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${n??"unknown"} pending=${v.length}`);i=W(t,{name:n,arguments:s})}i&&(oe.tool+=1,K(i,o,a),G(i),N(i.id),re(i,t))}),!0),sendBlockReply:e=>(le("block_reply",()=>{oe.block+=1,isNonEmptyString(e.text)&&(ae=!0!==e.isError?void 0:e.text)}),!0),sendFinalReply:t=>(le("final_reply",()=>{var r;(oe.final+=1,!0!==t.isError)?((t=>{const r=Y(t).filter(e=>!q.has(e));if(0===r.length)return;for(const e of r)q.add(e);const n=(async()=>{let t=[];try{t=await resolveToolArtifactLinks({toolCallId:`final:${e.runId??e.requestId}`,toolName:"final_reply_media",payloadMediaUrls:r})}catch(t){for(const e of r)q.delete(e);return void e.logger.warn?.(`[im-sink] final media enrichment failed requestId=${e.requestId} error=${t instanceof Error?t.message:String(t)}`)}if(0!==t.length)await new Promise(r=>{e.callbackQueue.enqueueSync(()=>{try{if(e.abortSignal.aborted||e.stream.isTerminated)return;ee(t)}catch(t){e.logger.warn?.(`[im-sink] final media enrichment apply failed requestId=${e.requestId} error=${t instanceof Error?t.message:String(t)}`)}finally{r()}})});else for(const e of r)q.delete(e)})();te(n)})(t),isNonEmptyString(t.text)&&(ae=void 0,l||(r=t.text,i="",a+=r,P(a),C(r),l=!0))):ae=t.text}),!0),waitForIdle:async()=>{for(let t=0;t<16;t++){if(await e.callbackQueue.waitForIdle(),0===$.size)return;await Promise.allSettled([...$])}await e.callbackQueue.waitForIdle()},getQueuedCounts:()=>({tool:oe.tool,block:oe.block,final:oe.final}),getAccumulatedBody:()=>o.text,getTerminalErrorText:()=>ae,markComplete:r=>{A(),(y.length>0||b.size>0)&&e.logger.debug?.(`[im-sink] dropping uncommitted lifecycle tool signals requestId=${e.requestId} legacy_hints=${y.length} buffered=${b.size}`),i="",P(a),c.receivedSnapshotCount>0&&logChatLatency(e.logger,"claw.text_preview.settled",e.requestId,t,{text_preview_snapshot_count:c.receivedSnapshotCount,text_preview_streaming_publish_count:c.streamingPublishCount,text_preview_terminal_publish_count:c.terminalPublishCount,text_preview_coalesced_snapshot_count:Math.max(0,c.receivedSnapshotCount-c.streamingPublishCount-c.terminalPublishCount),text_preview_publish_interval_ms:200});for(const t of h.values())try{"success"===r&&isUploadArtifactToolName(t.name)&&!t.durableUploadFilesEmitted&&re(t,{toolCallId:t.id,toolName:t.name}),"success"===r&&G(t),N(t.id)}catch(r){const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)}y.length=0,b.clear(),ie=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,disableBlockStreaming:!0,onAgentRunStart:r=>{e.onAgentRunStart?.(r),logChatLatency(e.logger,"claw.runtime.run_started",e.requestId,t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${r} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:n=>{le("partial",()=>{"string"==typeof n.text&&(!r&&n.text.length>0&&(r=!0,logChatLatency(e.logger,"claw.model.first_delta",e.requestId,t)),i=n.text,(e=>{const t=m;if(!t){if(o.text===e)return;return w(e),void(m={targetBody:e,receivedSnapshotCount:1,streamingPublishCount:1,terminalPublishCount:0})}t.targetBody!==e&&(t.targetBody=e,t.receivedSnapshotCount+=1,T(t))})(a+i))})},onReasoningStream:t=>{le("reasoning",()=>{if(!isNonEmptyString(t.text))return;const r=t.text,n=p??(t=>{E();const r={partIndex:_,displaySeq:S,text:t,publishedText:t,receivedSnapshotCount:1,streamingPublishCount:1,startedAtMs:Date.now(),startedAtMonotonicMs:performance.now()};return e.stream.addPart(M(r,t,"streaming")),_+=1,S+=1,p=r,r})(r);r!==n.text&&(n.text.startsWith(r)||(n.text=r,n.receivedSnapshotCount+=1,O(n)))})},onReasoningEnd:()=>{le("reasoning_end",()=>{A()})},onAssistantMessageStart:()=>{le("assistant_message_start",()=>{i="",l=!1,P(a),e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)})},onToolStart:r=>{le("tool_start",()=>{const s="string"==typeof r.phase?r.phase:"start",o=resolveRuntimeToolName(r)??"tool",a=resolveRuntimeToolCallId(r),i=resolveRuntimeToolArguments(r);if("update"===s)return void(a&&W(B(a),{name:o,arguments:i,pending:!0}));if(a&&n.set(a,Date.now()),logChatLatency(e.logger,"claw.tool.start",e.requestId,t,{tool:o}),a){W(a,{name:o,arguments:i,toolStartObserved:!0,pending:!0});const e=U(o);return void(e&&e!==a&&!f.has(a)&&(L(a,e),W(e,{name:o,arguments:i,toolStartObserved:!0,pending:!0})))}const l=(d=o,v.map(e=>h.get(e)).filter(e=>void 0!==e&&(!d||e.name===d)&&e.itemEventObserved));var d;if(1===l.length)return void W(l[0].id,{name:o,arguments:i,toolStartObserved:!0,pending:!0});if(l.length>1)return void e.logger.debug?.(`[im-sink] ambiguous id-less tool start ignored requestId=${e.requestId} channel=${e.channelId} tool=${o} candidates=${l.length}`);const u=[...b.entries()].filter(([,e])=>!e.name||e.name===o||"tool"===e.name);1!==u.length?y.push({name:o,arguments:i}):W(u[0][0],{name:o,arguments:i,toolStartObserved:!0,pending:!0})})},onItemEvent:e=>{le("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),s=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);n&&s&&("update"!==r?se(n,s):W(B(n),{name:s,itemEventObserved:!0,pending:!0}))})}},commitAssistantMessage:e=>{le("assistant_message_commit",()=>{if(u.has(e.key))return;u.add(e.key),l=!0,ae=void 0,i="";const t=e.segments.filter(e=>"text"===e.type).map(e=>e.text).join("");a+=t,P(a);for(const t of e.segments)"text"!==t.type?H(t.id,t.name,t.arguments,!0):C(t.text)})},observeToolResult:r=>{le("tool_result_observation",()=>{const o=r.toolCallId.trim(),a=r.toolName.trim();if(!o||!a)return;if(!s.has(o)){s.add(o);const i=n.get(o);logChatLatency(e.logger,"claw.tool.result",e.requestId,t,{tool:a,tool_elapsed_ms:void 0===i?void 0:Math.max(0,Date.now()-i),outcome:r.isError?"error":"success",...r.isError?{error:summarizeObservedToolError(r.rawResult)??"error"}:{}})}const i=searchSourceProvider(r.rawResult)??"unknown";let l=0;try{const t=extractSearchSources({toolName:a,rawResult:r.rawResult,isError:r.isError});if(l=t.length,0===l)return;const n=ne(o,a);if(n)R(t,n.id);else{const e=z(B(o));e.name??=a,e.sources=D(e.sources,t)}e.logger.debug?.(`[im-sink] search source projection requestId=${e.requestId} tool=${a} provider=${i} source_count=${l}`)}catch{e.logger.warn?.(`[im-sink] search source projection ignored requestId=${e.requestId} tool=${a} provider=${i} source_count=${l}`)}})},getSourceCount:()=>oe.source}}
1
+ import{toMessagePart}from"../message-client-types.js";import{isUploadArtifactToolName,resolveToolArtifactLinks}from"../upload/tool-artifact-enrichment.js";import{sessionWorkspaceDir,takeFinalizeAttachments}from"../upload/delivery-ledger.js";import{resolveRuntimeToolArguments,resolveRuntimeToolCallId,resolveRuntimeToolName}from"./tool-envelope-metadata.js";import{extractSearchSources,mergeSearchSource,searchSourceProvider}from"./search-source-extractor.js";import{logChatLatency}from"../chat-latency.js";const STREAMING_SNAPSHOT_PUBLISH_INTERVAL_MS=200,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}}function asToolParams(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e}const summarizeObservedToolError=e=>{if(null==e)return;if("string"==typeof e)return e.slice(0,180);if("object"!=typeof e)return String(e).slice(0,180);const t=e;for(const e of["error","message","blockReason","details","text"]){const r=t[e];if("string"==typeof r&&r.trim())return r.slice(0,180);if(isPlainRecord(r)&&"string"==typeof r.message&&r.message.trim())return r.message.slice(0,180)}try{const t=JSON.stringify(e);return t?t.slice(0,180):void 0}catch{return}};export function createImReplySink(e){const t=e.startedAtMs??Date.now();let r=!1;const n=new Map,s=new Set,o={text:""};let a="",i="",l=!1,d=!1;const u=new Set;let m;const c={receivedSnapshotCount:0,streamingPublishCount:0,terminalPublishCount:0},g=[];let p;const h=new Map,f=new Map,v=[],y=[],b=new Map,I=new Map;let _=0,S=1;const x=(e,t={})=>({schema_version:1,seq:e,...t}),$=new Set,q=new Set,w=t=>{t!==o.text&&(t.startsWith(o.text)?e.stream.appendBody(t.slice(o.text.length)):e.stream.setBody(t),o.text=t)},T=e=>{void 0===e.publishTimer&&(e.publishTimer=setTimeout(()=>{e.publishTimer=void 0,le("body_preview_flush",()=>{m===e&&(e=>{o.text!==e.targetBody&&(w(e.targetBody),e.streamingPublishCount+=1)})(e)})},200))},k=e=>{const t=m;t?(void 0!==t.publishTimer&&(clearTimeout(t.publishTimer),t.publishTimer=void 0),t.targetBody=e,o.text!==e&&(w(e),t.terminalPublishCount+=1),c.receivedSnapshotCount+=t.receivedSnapshotCount,c.streamingPublishCount+=t.streamingPublishCount,c.terminalPublishCount+=t.terminalPublishCount,m=void 0):w(e)},P=t=>{if(void 0!==t.partIndex)return;const r=_,n=S;e.stream.addPart(toMessagePart({type:"text",text:t.text,state:"done",_seq:n,data:x(n)})),t.partIndex=r,_+=1,S+=1},E=()=>{if(!d){d=!0;for(const e of g)P(e)}},C=e=>{const t={text:e};g.push(t),d&&P(t)},R=(t,r)=>{for(const n of t){const t=I.get(n.url);if(t){const r=mergeSearchSource(t,n);if(r.title===t.title&&r.snippet===t.snippet)continue;const s={...r,partIndex:t.partIndex,displaySeq:t.displaySeq,toolUseId:t.toolUseId};e.stream.replacePart(t.partIndex,toMessagePart({type:"source",url:s.url,...s.title?{title:s.title}:{},...s.snippet?{snippet:s.snippet}:{},tool_use_id:s.toolUseId,_seq:s.displaySeq,data:x(s.displaySeq)})),I.set(n.url,s);continue}const s={...n,partIndex:_,displaySeq:S,toolUseId:r};e.stream.addPart(toMessagePart({type:"source",url:n.url,...n.title?{title:n.title}:{},...n.snippet?{snippet:n.snippet}:{},tool_use_id:r,_seq:s.displaySeq,data:x(s.displaySeq)})),I.set(n.url,s),_+=1,S+=1,oe.source+=1}},M=(e,t,r,n)=>toMessagePart({type:"thinking",text:t,state:r,_seq:e.displaySeq,_started_at_ms:e.startedAtMs,...void 0!==n?{_duration_ms:n}:{},data:x(e.displaySeq,{started_at_ms:e.startedAtMs,...void 0!==n?{duration_ms:n}:{}})}),O=t=>{void 0===t.publishTimer&&(t.publishTimer=setTimeout(()=>{t.publishTimer=void 0,le("reasoning_snapshot_flush",()=>{p===t&&(t=>{t.publishedText!==t.text&&(e.stream.replacePart(t.partIndex,M(t,t.text,"streaming")),t.publishedText=t.text,t.streamingPublishCount+=1)})(t)})},200))},A=()=>{const r=p;if(!r)return;void 0!==r.publishTimer&&(clearTimeout(r.publishTimer),r.publishTimer=void 0);const n=Math.max(0,Math.round(performance.now()-r.startedAtMonotonicMs));e.stream.replacePart(r.partIndex,M(r,r.text,"done",n)),p=void 0,logChatLatency(e.logger,"claw.reasoning.settled",e.requestId,t,{reasoning_duration_ms:n,reasoning_chars:r.text.length,reasoning_snapshot_count:r.receivedSnapshotCount,reasoning_streaming_publish_count:r.streamingPublishCount,reasoning_coalesced_snapshot_count:Math.max(0,r.receivedSnapshotCount-r.streamingPublishCount),reasoning_publish_interval_ms:200})},N=e=>{const t=v.indexOf(e);t>=0&&v.splice(t,1)},U=e=>{const t=e?v.filter(t=>h.get(t)?.name===e):v;return 1===t.length?t[0]:void 0},j=e=>{const t=y.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 y.splice(n,1),r},L=(e,t)=>{if(f.set(e,t),e===t)return;const r=b.get(e);if(!r)return;const n=z(t);Q(n,r),b.delete(e);const s=h.get(t);s&&(X(s,n),b.delete(t))},B=e=>f.get(e)??e,F=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),z=e=>{let t=b.get(e);return t||(t={},b.set(e,t)),t},D=(e,t)=>{if(!e?.length&&!t?.length)return;const r=[],n=new Map;for(const s of[...e??[],...t??[]]){const e=n.get(s.url);void 0===e?(n.set(s.url,r.length),r.push(s)):r[e]=mergeSearchSource(r[e],s)}return r},Q=(e,t)=>{e.name??=t.name,e.arguments??=t.arguments,t.toolStartObserved&&(e.toolStartObserved=!0),t.itemEventObserved&&(e.itemEventObserved=!0),t.progress?.length&&(e.progress=[...e.progress??[],...t.progress]),e.sources=D(e.sources,t.sources)},W=(t,r,n)=>{const s="tool"===t.name&&"tool"!==r?r:t.name,o=!F(t.arguments)&&F(n)?n:t.arguments;s===t.name&&o===t.arguments||(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:s,arguments:o,state:t.state,_seq:t.displaySeq,data:x(t.displaySeq)})),t.name=s,t.arguments=o)},J=(t,r,n,s)=>{const o=h.get(t);if(o)return W(o,r,n),s&&!v.includes(t)&&"streaming"===o.state&&v.push(t),o;E();const a=b.get(t),i="tool"!==r?r:a?.name&&"tool"!==a.name?a.name:r,l=F(n)?n:a?.arguments??n,d=((t,r,n)=>{const s=_,o=S;return e.stream.addPart(toMessagePart({type:"tool_use",id:t,name:r,arguments:n,state:"streaming",_seq:o,data:x(o)})),_+=1,S+=1,{partIndex:s,displaySeq:o}})(t,i,l),u={id:t,name:i,arguments:l,partIndex:d.partIndex,displaySeq:d.displaySeq,state:"streaming",itemEventObserved:!0===a?.itemEventObserved,toolStartObserved:!0===a?.toolStartObserved};return h.set(t,u),(s||u.toolStartObserved||u.itemEventObserved)&&(v.includes(t)||"streaming"!==u.state||v.push(t)),a&&X(u,a),b.delete(t),u},H=(e,t)=>{const r=(e=>{const t=f.get(e);return t?h.get(t):h.get(e)})(e);if(r)return void 0===t.name&&void 0===t.arguments||W(r,t.name??r.name,t.arguments??r.arguments),t.toolStartObserved&&(r.toolStartObserved=!0),t.itemEventObserved&&(r.itemEventObserved=!0),(t.pending||t.toolStartObserved||t.itemEventObserved)&&!v.includes(r.id)&&"streaming"===r.state&&v.push(r.id),r;const n=z(e);void 0!==t.name&&(n.name=t.name),void 0!==t.arguments&&(n.arguments=t.arguments),t.toolStartObserved&&(n.toolStartObserved=!0),t.itemEventObserved&&(n.itemEventObserved=!0)},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",_seq:t.displaySeq,data:x(t.displaySeq)})),t.state="done")},G=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(G).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${G(t)}`).join(",")}}`},V=(t,r,n)=>{const s=`${n?"1":"0"}:${G(r)}`;if(t.progressSignature===s)return;const o=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=_;return e.stream.addPart(o),t.progressPartIndex=r,t.progressSignature=s,void(_+=1)}e.stream.replacePart(t.progressPartIndex,o),t.progressSignature=s},X=(e,t)=>{if(t.progress?.length){for(const r of t.progress)V(e,r.content,r.isError);K(e),N(e.id)}t.sources?.length&&R(t.sources,e.id)},Y=e=>[...new Set([e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e)).map(e=>e.trim()).filter(Boolean))],Z=(e,t=[])=>{const r=[...new Set([...Y(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.":""},ee=t=>{for(const r of t)e.stream.addPart(toMessagePart({type:"resource_link",uri:r.uri,name:r.name,...r.mimeType?{mimeType:r.mimeType}:{}})),_+=1},te=e=>{$.add(e),e.finally(()=>{$.delete(e)})},re=(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),s=(async()=>{let s=[];try{s=await resolveToolArtifactLinks({toolCallId:t.id,toolName:t.name,rawResult:n,rawText:"string"==typeof r.text?r.text:void 0,params:asToolParams(t.arguments),cwd:sessionWorkspaceDir(e.sessionKey),payloadMediaUrls:Y(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!==s.length?(e.logger.info?.(`[im-sink] tool artifact enrichment resolved ${s.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=s.map(e=>e.uri);V(t,Z(r,n),!0===r.isError),((t,r,n)=>{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),_+=1,ee(r))})(t,s,!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}`)});te(s)},ne=(e,t,r)=>{const n=f.get(e);if(n)return H(n,{name:t,arguments:r});const s=H(e,{name:t,arguments:r});if(s)return s;const o=U(t);if(o)return L(e,o),H(o,{name:t,arguments:r});H(e,{name:t,arguments:r})},se=(e,t)=>{const r=f.get(e);if(r)return H(r,{name:t,itemEventObserved:!0,pending:!0});const n=H(e,{name:t,itemEventObserved:!0,pending:!0});if(n)return n;const s=(e=>{const t=v.filter(t=>{const r=h.get(t);return!(void 0===r||e&&r.name!==e||r.itemEventObserved)});return 1===t.length?t[0]:void 0})(t);if(s)return L(e,s),H(s,{name:t,itemEventObserved:!0,pending:!0});const o=j(t);H(e,{name:t,arguments:o?.arguments,itemEventObserved:!0,pending:!0})},oe={tool:0,block:0,final:0,source:0};let ae,ie=!1;const le=(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):!ie||(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=>(le("tool_progress",()=>{const r=resolveRuntimeToolCallId(t),n=resolveRuntimeToolName(t),s=resolveRuntimeToolArguments(t),o=Z(t),a=!0===t.isError;let i;if(r){if(i=ne(r,n,s),!i){const e=z(r);return n&&(e.name=n),F(s)&&(e.arguments=s),e.progress=[...e.progress??[],{content:o,isError:a}],void(oe.tool+=1)}}else{const t=n?U(n):U();if(!t)return j(n),void e.logger.debug?.(`[im-sink] unattributed tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${n??"unknown"} pending=${v.length}`);i=H(t,{name:n,arguments:s})}i&&(oe.tool+=1,V(i,o,a),K(i),N(i.id),re(i,t))}),!0),sendBlockReply:e=>(le("block_reply",()=>{oe.block+=1,isNonEmptyString(e.text)&&(ae=!0!==e.isError?void 0:e.text)}),!0),sendFinalReply:t=>(le("final_reply",()=>{var r;(oe.final+=1,!0!==t.isError)?((t=>{const r=Y(t).filter(e=>!q.has(e));if(0===r.length)return;for(const e of r)q.add(e);const n=(async()=>{let t=[];try{t=await resolveToolArtifactLinks({toolCallId:`final:${e.runId??e.requestId}`,toolName:"final_reply_media",payloadMediaUrls:r})}catch(t){for(const e of r)q.delete(e);return void e.logger.warn?.(`[im-sink] final media enrichment failed requestId=${e.requestId} error=${t instanceof Error?t.message:String(t)}`)}if(0!==t.length)await new Promise(r=>{e.callbackQueue.enqueueSync(()=>{try{if(e.abortSignal.aborted||e.stream.isTerminated)return;ee(t)}catch(t){e.logger.warn?.(`[im-sink] final media enrichment apply failed requestId=${e.requestId} error=${t instanceof Error?t.message:String(t)}`)}finally{r()}})});else for(const e of r)q.delete(e)})();te(n)})(t),isNonEmptyString(t.text)&&(ae=void 0,l||(r=t.text,i="",a+=r,k(a),C(r),l=!0))):ae=t.text}),!0),waitForIdle:async()=>{for(let t=0;t<16&&(await e.callbackQueue.waitForIdle(),0!==$.size);t++)await Promise.allSettled([...$]);await e.callbackQueue.waitForIdle();const t=takeFinalizeAttachments(e.sessionKey);t.length>0&&!e.abortSignal.aborted&&!e.stream.isTerminated&&ee(t)},getQueuedCounts:()=>({tool:oe.tool,block:oe.block,final:oe.final}),getAccumulatedBody:()=>o.text,getTerminalErrorText:()=>ae,markComplete:r=>{A(),(y.length>0||b.size>0)&&e.logger.debug?.(`[im-sink] dropping uncommitted lifecycle tool signals requestId=${e.requestId} legacy_hints=${y.length} buffered=${b.size}`),i="",k(a),c.receivedSnapshotCount>0&&logChatLatency(e.logger,"claw.text_preview.settled",e.requestId,t,{text_preview_snapshot_count:c.receivedSnapshotCount,text_preview_streaming_publish_count:c.streamingPublishCount,text_preview_terminal_publish_count:c.terminalPublishCount,text_preview_coalesced_snapshot_count:Math.max(0,c.receivedSnapshotCount-c.streamingPublishCount-c.terminalPublishCount),text_preview_publish_interval_ms:200});for(const t of h.values())try{"success"===r&&isUploadArtifactToolName(t.name)&&!t.durableUploadFilesEmitted&&re(t,{toolCallId:t.id,toolName:t.name}),"success"===r&&K(t),N(t.id)}catch(r){const n=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${t.id} error=${n}`)}y.length=0,b.clear(),ie=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,disableBlockStreaming:!0,onAgentRunStart:r=>{e.onAgentRunStart?.(r),logChatLatency(e.logger,"claw.runtime.run_started",e.requestId,t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${r} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:n=>{le("partial",()=>{"string"==typeof n.text&&(!r&&n.text.length>0&&(r=!0,logChatLatency(e.logger,"claw.model.first_delta",e.requestId,t)),i=n.text,(e=>{const t=m;if(!t){if(o.text===e)return;return w(e),void(m={targetBody:e,receivedSnapshotCount:1,streamingPublishCount:1,terminalPublishCount:0})}t.targetBody!==e&&(t.targetBody=e,t.receivedSnapshotCount+=1,T(t))})(a+i))})},onReasoningStream:t=>{le("reasoning",()=>{if(!isNonEmptyString(t.text))return;const r=t.text,n=p??(t=>{E();const r={partIndex:_,displaySeq:S,text:t,publishedText:t,receivedSnapshotCount:1,streamingPublishCount:1,startedAtMs:Date.now(),startedAtMonotonicMs:performance.now()};return e.stream.addPart(M(r,t,"streaming")),_+=1,S+=1,p=r,r})(r);r!==n.text&&(n.text.startsWith(r)||(n.text=r,n.receivedSnapshotCount+=1,O(n)))})},onReasoningEnd:()=>{le("reasoning_end",()=>{A()})},onAssistantMessageStart:()=>{le("assistant_message_start",()=>{i="",l=!1,k(a),e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)})},onToolStart:r=>{le("tool_start",()=>{const s="string"==typeof r.phase?r.phase:"start",o=resolveRuntimeToolName(r)??"tool",a=resolveRuntimeToolCallId(r),i=resolveRuntimeToolArguments(r);if("update"===s)return void(a&&H(B(a),{name:o,arguments:i,pending:!0}));if(a&&n.set(a,Date.now()),logChatLatency(e.logger,"claw.tool.start",e.requestId,t,{tool:o}),a){H(a,{name:o,arguments:i,toolStartObserved:!0,pending:!0});const e=U(o);return void(e&&e!==a&&!f.has(a)&&(L(a,e),H(e,{name:o,arguments:i,toolStartObserved:!0,pending:!0})))}const l=(d=o,v.map(e=>h.get(e)).filter(e=>void 0!==e&&(!d||e.name===d)&&e.itemEventObserved));var d;if(1===l.length)return void H(l[0].id,{name:o,arguments:i,toolStartObserved:!0,pending:!0});if(l.length>1)return void e.logger.debug?.(`[im-sink] ambiguous id-less tool start ignored requestId=${e.requestId} channel=${e.channelId} tool=${o} candidates=${l.length}`);const u=[...b.entries()].filter(([,e])=>!e.name||e.name===o||"tool"===e.name);1!==u.length?y.push({name:o,arguments:i}):H(u[0][0],{name:o,arguments:i,toolStartObserved:!0,pending:!0})})},onItemEvent:e=>{le("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),s=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);n&&s&&("update"!==r?se(n,s):H(B(n),{name:s,itemEventObserved:!0,pending:!0}))})}},commitAssistantMessage:e=>{le("assistant_message_commit",()=>{if(u.has(e.key))return;u.add(e.key),l=!0,ae=void 0,i="";const t=e.segments.filter(e=>"text"===e.type).map(e=>e.text).join("");a+=t,k(a);for(const t of e.segments)"text"!==t.type?J(t.id,t.name,t.arguments,!0):C(t.text)})},observeToolResult:r=>{le("tool_result_observation",()=>{const o=r.toolCallId.trim(),a=r.toolName.trim();if(!o||!a)return;if(!s.has(o)){s.add(o);const i=n.get(o);logChatLatency(e.logger,"claw.tool.result",e.requestId,t,{tool:a,tool_elapsed_ms:void 0===i?void 0:Math.max(0,Date.now()-i),outcome:r.isError?"error":"success",...r.isError?{error:summarizeObservedToolError(r.rawResult)??"error"}:{}})}const i=searchSourceProvider(r.rawResult)??"unknown";let l=0;try{const t=extractSearchSources({toolName:a,rawResult:r.rawResult,isError:r.isError});if(l=t.length,0===l)return;const n=ne(o,a);if(n)R(t,n.id);else{const e=z(B(o));e.name??=a,e.sources=D(e.sources,t)}e.logger.debug?.(`[im-sink] search source projection requestId=${e.requestId} tool=${a} provider=${i} source_count=${l}`)}catch{e.logger.warn?.(`[im-sink] search source projection ignored requestId=${e.requestId} tool=${a} provider=${i} source_count=${l}`)}})},getSourceCount:()=>oe.source}}
@@ -0,0 +1 @@
1
+ import{extractDeliverablePaths,isGeneratingToolName}from"./deliverable-paths.js";export const deliverableFileExtractor={name:"write_exec_deliverable",canExtract:e=>isGeneratingToolName(e.toolName),extract:e=>extractDeliverablePaths({toolName:e.toolName,params:e.params,result:e.rawResult,rawText:e.rawText,cwd:e.cwd}).map(e=>({filePath:e}))};
@@ -0,0 +1 @@
1
+ import{extname,isAbsolute,resolve}from"node:path";const GENERATING_TOOLS=new Set(["write","exec","bash"]),DELIVERABLE_EXT=new Set([".doc",".docx",".xls",".xlsx",".ppt",".pptx",".pdf",".csv",".odt",".ods",".odp"]),PATH_IN_TEXT=/(?:^|[\s"'=`])((?:\/|\.\/)?[^\s"'=`<>]+\.(?:docx?|xlsx?|pptx?|pdf|csv|odt|ods|odp))\b/gi,EXEC_PRODUCED_PATH=/(?:wrote|saved|exported|generated)\b[^\n]*?((?:\/|\.\/)?[^\s"'=`<>]+\.(?:docx?|xlsx?|pptx?|pdf|csv|odt|ods|odp))\b/gi,SKIP_PATH_PART=/(?:^|\/)(?:node_modules|\.git|proc|dev)(?:\/|$)/;export function isGeneratingToolName(t){return GENERATING_TOOLS.has(normalizeToolName(t))}export function isDeliverableFileName(t){return DELIVERABLE_EXT.has(extname(t).toLowerCase())}export function extractDeliverablePaths(t){const e=normalizeToolName(t.toolName);if(!GENERATING_TOOLS.has(e))return[];const o=new Set;"write"===e?(collectPathFields(t.params,o),collectWriteResultText(t.result,o),t.rawText&&collectFromText(t.rawText,o)):(collectPathFields(t.result,o),collectExecProducedPaths(t.result,t.rawText,o));const s=[];for(const e of o){const o=resolveAgainst(e,t.cwd);o&&!SKIP_PATH_PART.test(o)&&(isDeliverableFileName(o)&&s.push(o))}return s}function normalizeToolName(t){return(t??"").trim().toLowerCase()}function resolveAgainst(t,e){const o=t.trim();if(o)return isAbsolute(o)?resolve(o):e?resolve(e,o):void 0}function collectPathFields(t,e){if(!t||"object"!=typeof t||Array.isArray(t))return;const o=t;for(const t of["path","file_path","filePath","output","outfile","dest"]){const s=o[t];"string"==typeof s&&looksLikePath(s)&&e.add(s)}}function collectWriteResultText(t,e){if("string"==typeof t)return void collectFromText(t,e);if(!t||"object"!=typeof t)return;const o=t;if(Array.isArray(o.content))for(const t of o.content){if(!t||"object"!=typeof t)continue;const o=t.text;"string"==typeof o&&collectFromText(o,e)}}function collectExecProducedPaths(t,e,o){const s=[];if(e&&s.push(e),"string"==typeof t&&s.push(t),t&&"object"==typeof t){const e=t;for(const t of["stdout","output","text"])"string"==typeof e[t]&&s.push(e[t])}for(const t of s){EXEC_PRODUCED_PATH.lastIndex=0;for(const e of t.matchAll(EXEC_PRODUCED_PATH))e[1]&&o.add(e[1])}}function collectFromText(t,e){PATH_IN_TEXT.lastIndex=0;for(const o of t.matchAll(PATH_IN_TEXT)){const t=o[1];t&&e.add(t)}}function looksLikePath(t){const e=t.trim();return!(!e||/\s/.test(e))&&(isDeliverableFileName(e)&&(isAbsolute(e)||e.includes("/")||e.includes(".")))}
@@ -0,0 +1 @@
1
+ import{resolve}from"node:path";const LEDGER_TTL_MS=18e5,MAX_LEDGER_ENTRIES=256,pending=new Map,sessionWorkspace=new Map;export function rememberSessionWorkspace(e,n){if(e&&n&&(sessionWorkspace.set(e,n),sessionWorkspace.size>256)){const e=sessionWorkspace.keys().next().value;void 0!==e&&sessionWorkspace.delete(e)}}export function sessionWorkspaceDir(e){if(e)return sessionWorkspace.get(e)}export function recordGeneratedDeliverable(e,n){if(!n)return;const t=resolve(e);if(pruneExpired(),pending.size>=256&&!pending.has(ledgerKey(n,t))){const e=pending.keys().next().value;void 0!==e&&pending.delete(e)}pending.set(ledgerKey(n,t),{sessionKey:n,filePath:t,expiresAt:Date.now()+18e5})}export function noteUploadedDeliverable(e,n){const t=resolve(e);for(const[e,s]of pending)s.filePath===t&&(n&&s.sessionKey!==n||pending.delete(e))}export function outstandingDeliverables(e){if(pruneExpired(),!e)return[];const n=[],t=new Set;for(const s of pending.values())s.sessionKey===e&&(t.has(s.filePath)||(t.add(s.filePath),n.push(s.filePath)));return n}const finalizeAttachments=new Map;export function stashFinalizeAttachments(e,n){if(!e||0===n.length)return;const t=finalizeAttachments.get(e)??[];t.push(...n),finalizeAttachments.set(e,t)}export function takeFinalizeAttachments(e){if(!e)return[];const n=finalizeAttachments.get(e)??[];return finalizeAttachments.delete(e),n}export function resetDeliveryLedgerForTests(){pending.clear(),sessionWorkspace.clear(),finalizeAttachments.clear()}function ledgerKey(e,n){return`${e}\0${n}`}function pruneExpired(){const e=Date.now();for(const[n,t]of pending)t.expiresAt<=e&&pending.delete(n)}
@@ -0,0 +1 @@
1
+ import{extractDeliverablePaths}from"./deliverable-paths.js";import{recordGeneratedDeliverable,rememberSessionWorkspace,sessionWorkspaceDir}from"./delivery-ledger.js";export function rememberDeliveryWorkspace(e,r){e&&rememberSessionWorkspace(e,r)}export function observeToolCallForDelivery(e,r){if(e.error||!r)return;const o=extractDeliverablePaths({toolName:e.toolName,params:e.params,result:e.result,cwd:sessionWorkspaceDir(r)});for(const e of o)recordGeneratedDeliverable(e,r)}
@@ -0,0 +1 @@
1
+ import{outstandingDeliverables,rememberSessionWorkspace,stashFinalizeAttachments}from"./delivery-ledger.js";import{getUploadDeps,uploadLocalFile}from"./upload-file.js";const MAX_FINALIZE_UPLOADS=5;export async function fileDeliveryFinalizeDecision(e){if(e.stopHookActive||!e.sessionKey)return;e.cwd&&rememberSessionWorkspace(e.sessionKey,e.cwd);const s=getUploadDeps();if(!s)return;const i=outstandingDeliverables(e.sessionKey).slice(0,5),o=[];for(const e of i)try{const i=await uploadLocalFile(e,s.gatewayUrl,s.keys);o.push({uri:i.uri,name:i.name,...i.mimeType?{mimeType:i.mimeType}:{}})}catch(i){s.logger?.warn?.(`[file-delivery] finalize upload failed path=${e}: ${i instanceof Error?i.message:String(i)}`)}stashFinalizeAttachments(e.sessionKey,o)}
@@ -1 +1 @@
1
- export{BEEOS_UPLOAD_TOOL_NAME,BEEOS_UPLOAD_TOOL_SCHEMA,BEEOS_UPLOAD_TOOL_DESCRIPTION,executeUpload}from"./tool.js";export{BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME,BEEOS_DESKTOP_SCREENSHOT_TOOL_SCHEMA,BEEOS_DESKTOP_SCREENSHOT_TOOL_DESCRIPTION,executeDesktopScreenshot}from"./desktop-screenshot-tool.js";export{writeUploadToolResult,cleanupUploadToolResultCache,writeEnrichmentCache,readEnrichmentCache,resolveUploadToolResultForEnrichment}from"./result-cache.js";export{extractUploadMediaUrls,extractUploadResourceLinks}from"./extract-upload-uris.js";export{resolveToolArtifactLinks,isUploadArtifactToolName,isLocalMediaPath,isRemoteOrPlatformMediaUri}from"./tool-artifact-enrichment.js";export{handleFileStage}from"./file-stage.js";export{uploadLocalFile,setUploadDeps,getUploadDeps}from"./upload-file.js";export{registerLocalFileExtractor,tryExtractLocalFiles}from"./local-file-extractors.js";
1
+ export{BEEOS_UPLOAD_TOOL_NAME,BEEOS_UPLOAD_TOOL_SCHEMA,BEEOS_UPLOAD_TOOL_DESCRIPTION,executeUpload}from"./tool.js";export{BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME,BEEOS_DESKTOP_SCREENSHOT_TOOL_SCHEMA,BEEOS_DESKTOP_SCREENSHOT_TOOL_DESCRIPTION,executeDesktopScreenshot}from"./desktop-screenshot-tool.js";export{writeUploadToolResult,cleanupUploadToolResultCache,writeEnrichmentCache,readEnrichmentCache,resolveUploadToolResultForEnrichment}from"./result-cache.js";export{extractUploadMediaUrls,extractUploadResourceLinks}from"./extract-upload-uris.js";export{resolveToolArtifactLinks,isUploadArtifactToolName,isLocalMediaPath,isRemoteOrPlatformMediaUri}from"./tool-artifact-enrichment.js";export{handleFileStage}from"./file-stage.js";export{uploadLocalFile,setUploadDeps,getUploadDeps}from"./upload-file.js";export{registerLocalFileExtractor,tryExtractLocalFiles}from"./local-file-extractors.js";export{deliverableFileExtractor}from"./deliverable-extractor.js";
@@ -1 +1 @@
1
- import{basename}from"node:path";import{BEEOS_UPLOAD_TOOL_NAME}from"./tool.js";import{BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME}from"./desktop-screenshot-tool.js";import{extractUploadResourceLinks}from"./extract-upload-uris.js";import{resolveUploadToolResultForEnrichment,writeEnrichmentCache}from"./result-cache.js";import{tryExtractLocalFiles}from"./local-file-extractors.js";import{getUploadDeps,uploadLocalFile}from"./upload-file.js";const UPLOAD_TOOL_NAMES=new Set([BEEOS_UPLOAD_TOOL_NAME,BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME]);export function isUploadArtifactToolName(e){if(!e)return!1;if(UPLOAD_TOOL_NAMES.has(e))return!0;const t=e.toLowerCase();return t.includes("beeos_upload")||t.includes("beeos upload file")||"beeos file upload"===t||t.includes("beeos_desktop_screenshot")||t.includes("desktop screenshot")}export function isRemoteOrPlatformMediaUri(e){const t=e.trim();return!!t&&(!!t.startsWith("beeos-file://")||(!(!t.startsWith("http://")&&!t.startsWith("https://"))||!!t.startsWith("data:")))}export function isLocalMediaPath(e){const t=e.trim();return!(!t||isRemoteOrPlatformMediaUri(t))&&(!!t.startsWith("file://")||!!t.startsWith("/"))}function filePathFromMediaUri(e){const t=e.trim();if(t.startsWith("file://"))try{return decodeURIComponent(new URL(t).pathname)}catch{return t.slice(7)}return t}function linkNameFromUri(e){if(e.startsWith("beeos-file://"))return e.slice(13)||"file";try{const t=e.includes("://")?new URL(e).pathname:e;return basename(t)||"file"}catch{return"file"}}function pushUnique(e,t,o){o.uri&&!t.has(o.uri)&&(t.add(o.uri),e.push(o))}export async function resolveToolArtifactLinks(e){const t=[],o=new Set,a=getUploadDeps(),i=a?.logger;let r=!1;if(isUploadArtifactToolName(e.toolName))try{const a=await resolveUploadToolResultForEnrichment(e.toolCallId,e.toolName===BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME?BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME:BEEOS_UPLOAD_TOOL_NAME);if(null!=a)for(const e of extractUploadResourceLinks(a))pushUnique(t,o,e)}catch(t){i?.warn?.(`[enrich] resolveUploadToolResultForEnrichment failed toolCallId=${e.toolCallId}: ${t instanceof Error?t.message:String(t)}`)}if(a){const l=new Set,s=tryExtractLocalFiles({toolCallId:e.toolCallId,toolName:e.toolName,rawResult:e.rawResult,rawText:e.rawText});for(const n of s)if(!l.has(n.filePath))try{i?.debug?.(`[enrich] Uploading local file ${n.filePath} for toolCallId=${e.toolCallId}`);const s=await uploadLocalFile(n.filePath,a.gatewayUrl,a.keys,{fileNameOverride:n.suggestedName});i?.info?.(`[enrich] Uploaded: fileId=${s.fileId} toolCallId=${e.toolCallId}`),l.add(n.filePath),pushUnique(t,o,{uri:s.uri,name:s.name,...s.mimeType?{mimeType:s.mimeType}:{}}),r=!0}catch(e){i?.warn?.(`[enrich] Upload failed for ${n.filePath}: ${e instanceof Error?e.message:String(e)}, skipping`)}for(const s of e.payloadMediaUrls??[]){if(!isLocalMediaPath(s))continue;const n=filePathFromMediaUri(s);if(!l.has(n))try{i?.debug?.(`[enrich] Uploading payload media path ${n} for toolCallId=${e.toolCallId}`);const s=await uploadLocalFile(n,a.gatewayUrl,a.keys);i?.info?.(`[enrich] Uploaded payload media: fileId=${s.fileId} toolCallId=${e.toolCallId}`),l.add(n),pushUnique(t,o,{uri:s.uri,name:s.name,...s.mimeType?{mimeType:s.mimeType}:{}}),r=!0}catch(e){i?.warn?.(`[enrich] Payload media upload failed for ${n}: ${e instanceof Error?e.message:String(e)}, skipping`)}}}for(const a of e.payloadMediaUrls??[])isRemoteOrPlatformMediaUri(a)&&pushUnique(t,o,{uri:a.trim(),name:linkNameFromUri(a.trim())});if(t.length>0&&(r||t.some(e=>e.uri.startsWith("beeos-file://")))){const o=t.map(e=>({uri:e.uri,name:e.name,...e.mimeType?{mimeType:e.mimeType}:{}}));writeEnrichmentCache(e.toolCallId,o).catch(t=>{i?.warn?.(`[enrich] Cache write failed for toolCallId=${e.toolCallId}: ${t instanceof Error?t.message:String(t)}`)})}return t}
1
+ import{basename}from"node:path";import{BEEOS_UPLOAD_TOOL_NAME}from"./tool.js";import{BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME}from"./desktop-screenshot-tool.js";import{extractUploadResourceLinks}from"./extract-upload-uris.js";import{resolveUploadToolResultForEnrichment,writeEnrichmentCache}from"./result-cache.js";import{tryExtractLocalFiles}from"./local-file-extractors.js";import{getUploadDeps,uploadLocalFile}from"./upload-file.js";const UPLOAD_TOOL_NAMES=new Set([BEEOS_UPLOAD_TOOL_NAME,BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME]);export function isUploadArtifactToolName(e){if(!e)return!1;if(UPLOAD_TOOL_NAMES.has(e))return!0;const t=e.toLowerCase();return t.includes("beeos_upload")||t.includes("beeos upload file")||"beeos file upload"===t||t.includes("beeos_desktop_screenshot")||t.includes("desktop screenshot")}export function isRemoteOrPlatformMediaUri(e){const t=e.trim();return!!t&&(!!t.startsWith("beeos-file://")||(!(!t.startsWith("http://")&&!t.startsWith("https://"))||!!t.startsWith("data:")))}export function isLocalMediaPath(e){const t=e.trim();return!(!t||isRemoteOrPlatformMediaUri(t))&&(!!t.startsWith("file://")||!!t.startsWith("/"))}function filePathFromMediaUri(e){const t=e.trim();if(t.startsWith("file://"))try{return decodeURIComponent(new URL(t).pathname)}catch{return t.slice(7)}return t}function linkNameFromUri(e){if(e.startsWith("beeos-file://"))return e.slice(13)||"file";try{const t=e.includes("://")?new URL(e).pathname:e;return basename(t)||"file"}catch{return"file"}}function pushUnique(e,t,o){o.uri&&!t.has(o.uri)&&(t.add(o.uri),e.push(o))}export async function resolveToolArtifactLinks(e){const t=[],o=new Set,a=getUploadDeps(),i=a?.logger;let r=!1;if(isUploadArtifactToolName(e.toolName))try{const a=await resolveUploadToolResultForEnrichment(e.toolCallId,e.toolName===BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME?BEEOS_DESKTOP_SCREENSHOT_TOOL_NAME:BEEOS_UPLOAD_TOOL_NAME);if(null!=a)for(const e of extractUploadResourceLinks(a))pushUnique(t,o,e)}catch(t){i?.warn?.(`[enrich] resolveUploadToolResultForEnrichment failed toolCallId=${e.toolCallId}: ${t instanceof Error?t.message:String(t)}`)}if(a){const l=new Set,s=tryExtractLocalFiles({toolCallId:e.toolCallId,toolName:e.toolName,rawResult:e.rawResult,rawText:e.rawText,params:e.params,cwd:e.cwd});for(const n of s)if(!l.has(n.filePath))try{i?.debug?.(`[enrich] Uploading local file ${n.filePath} for toolCallId=${e.toolCallId}`);const s=await uploadLocalFile(n.filePath,a.gatewayUrl,a.keys,{fileNameOverride:n.suggestedName});i?.info?.(`[enrich] Uploaded: fileId=${s.fileId} toolCallId=${e.toolCallId}`),l.add(n.filePath),pushUnique(t,o,{uri:s.uri,name:s.name,...s.mimeType?{mimeType:s.mimeType}:{}}),r=!0}catch(e){i?.warn?.(`[enrich] Upload failed for ${n.filePath}: ${e instanceof Error?e.message:String(e)}, skipping`)}for(const s of e.payloadMediaUrls??[]){if(!isLocalMediaPath(s))continue;const n=filePathFromMediaUri(s);if(!l.has(n))try{i?.debug?.(`[enrich] Uploading payload media path ${n} for toolCallId=${e.toolCallId}`);const s=await uploadLocalFile(n,a.gatewayUrl,a.keys);i?.info?.(`[enrich] Uploaded payload media: fileId=${s.fileId} toolCallId=${e.toolCallId}`),l.add(n),pushUnique(t,o,{uri:s.uri,name:s.name,...s.mimeType?{mimeType:s.mimeType}:{}}),r=!0}catch(e){i?.warn?.(`[enrich] Payload media upload failed for ${n}: ${e instanceof Error?e.message:String(e)}, skipping`)}}}for(const a of e.payloadMediaUrls??[])isRemoteOrPlatformMediaUri(a)&&pushUnique(t,o,{uri:a.trim(),name:linkNameFromUri(a.trim())});if(t.length>0&&(r||t.some(e=>e.uri.startsWith("beeos-file://")))){const o=t.map(e=>({uri:e.uri,name:e.name,...e.mimeType?{mimeType:e.mimeType}:{}}));writeEnrichmentCache(e.toolCallId,o).catch(t=>{i?.warn?.(`[enrich] Cache write failed for toolCallId=${e.toolCallId}: ${t instanceof Error?t.message:String(t)}`)})}return t}
@@ -1 +1 @@
1
- import{constants}from"node:fs";import{access,stat}from"node:fs/promises";import{uploadLocalFile}from"./upload-file.js";import{isBeeosFileUri}from"../files/beeos-file-uri.js";export const BEEOS_UPLOAD_TOOL_NAME="beeos_upload_file";export const BEEOS_UPLOAD_TOOL_DESCRIPTION="Upload 1-5 local files from this machine to the user via the BeeOS platform. Accepts only local filesystem paths; never pass beeos-file:// URIs because they are already BeeOS platform resources. If the call succeeds, the files are already delivered to the user — do not use any other tool to send them again. Any file type is accepted: text, images, documents, PDFs, archives, binaries.";export const BEEOS_UPLOAD_TOOL_SCHEMA={title:"BeeOS file upload",type:"object",description:BEEOS_UPLOAD_TOOL_DESCRIPTION,additionalProperties:!1,required:["paths"],properties:{paths:{type:"array",minItems:1,maxItems:5,description:"Local filesystem paths (1-5). Absolute or workspace-relative. Do not pass beeos-file:// URIs.",items:{type:"string",minLength:1,description:"Single local filesystem path, not a beeos-file:// URI."}}}};const MAX_PATHS=5,REQUEST_TIMEOUT_MS=12e4,MAX_CONCURRENCY=3,BEEOS_FILE_URI_UPLOAD_ERROR="beeos-file:// is already a BeeOS platform file URI, not a local path. Do not upload it; display it directly.";async function runWithConcurrency(e,t){const r=new Array(e.length);let o=0;async function a(){for(;o<e.length;){const t=o++;r[t]=await e[t]()}}const s=[];for(let r=0;r<Math.min(t,e.length);r++)s.push(a());return await Promise.all(s),r}async function validateFiles(e){return Promise.all(e.map(async e=>{try{await access(e,constants.R_OK);return(await stat(e)).isFile()?{path:e,ok:!0}:{path:e,ok:!1,error:"not a regular file"}}catch(t){const r=t.code;let o;return o="ENOENT"===r?"file not found":"ENOTDIR"===r?"path component is not a directory":"EACCES"===r?"permission denied":t instanceof Error?t.message:String(t),{path:e,ok:!1,error:o}}}))}export async function executeUpload(e,t,r,o){const a=parseParams(e);if(!a.ok)return errorResult(a.error);if(!t.agentGatewayUrl)return errorResult("agentGatewayUrl not configured — file upload requires Agent Gateway access");if(!r)return errorResult("agent key pair not available — cannot sign upload requests");const s=t.agentGatewayUrl,i=a.paths,n=(await validateFiles(i)).find(e=>!e.ok);if(n)return errorResult(`${n.path}: ${n.error}`);const l=[],p=i.map(e=>async()=>{try{return await uploadSingleFile(e,s,r,o)}catch(t){if(o?.aborted)throw t;return l.push(`${e}: ${t instanceof Error?t.message:String(t)}`),null}}),c=(await runWithConcurrency(p,3)).filter(e=>null!==e);if(0===c.length)return errorResult(`All uploads failed: ${l.join("; ")}`);const u=c.map(e=>({uri:`beeos-file://${e.fileId}`,name:e.fileName,mimeType:e.mimeType,fileId:e.fileId})),f=c.length===i.length?`Uploaded ${c.length} file(s): ${u.map(e=>e.name).join(", ")}`:`Uploaded ${c.length}/${i.length} file(s): ${u.map(e=>e.name).join(", ")}. Failed: ${l.join("; ")}`,m={ok:!0,files:u};return{...m,content:[{type:"text",text:f},...u.map(e=>({type:"resource_link",uri:e.uri,name:e.name,mimeType:e.mimeType}))],output:m,result:m,details:m,isError:!1}}async function uploadSingleFile(e,t,r,o){const a=await uploadLocalFile(e,t,r,{signal:o,timeoutMs:12e4});return{fileId:a.fileId,downloadUrl:"",fileName:a.name,mimeType:a.mimeType,size:a.size}}function parseParams(e){if(!e||"object"!=typeof e)return{ok:!1,error:"params must be an object"};const t=e;if(Object.keys(t).filter(e=>"paths"!==e).length>0)return{ok:!1,error:"only paths is allowed"};if(!Array.isArray(t.paths))return{ok:!1,error:"paths must be an array"};if(t.paths.length<1)return{ok:!1,error:"paths must contain at least 1 item"};if(t.paths.length>5)return{ok:!1,error:"paths must contain no more than 5 items"};const r=[];for(let e=0;e<t.paths.length;e++){const o=t.paths[e];if("string"!=typeof o||""===o.trim())return{ok:!1,error:`paths[${e}] must be a non-empty string`};const a=o.trim();if(isBeeosFileUri(a))return{ok:!1,error:BEEOS_FILE_URI_UPLOAD_ERROR};r.push(a)}return{ok:!0,paths:r}}function errorResult(e){const t={ok:!1,error:e};return{...t,content:[{type:"text",text:e}],output:t,result:t,details:t,isError:!0}}
1
+ import{constants}from"node:fs";import{access,stat}from"node:fs/promises";import{uploadLocalFile}from"./upload-file.js";import{isBeeosFileUri}from"../files/beeos-file-uri.js";export const BEEOS_UPLOAD_TOOL_NAME="beeos_upload_file";export const BEEOS_UPLOAD_TOOL_DESCRIPTION="Upload 1-5 local files from this machine to the user via the BeeOS platform. Accepts only local filesystem paths; never pass beeos-file:// URIs because they are already BeeOS platform resources. If the call succeeds, the files are already delivered to the user — do not use any other tool to send them again. Upload modified deliverables again even if the same path was uploaded earlier; unchanged contents may reuse an existing platform file. Any file type is accepted: text, images, documents, PDFs, archives, binaries.";export const BEEOS_UPLOAD_TOOL_SCHEMA={title:"BeeOS file upload",type:"object",description:BEEOS_UPLOAD_TOOL_DESCRIPTION,additionalProperties:!1,required:["paths"],properties:{paths:{type:"array",minItems:1,maxItems:5,description:"Local filesystem paths (1-5). Absolute or workspace-relative. Do not pass beeos-file:// URIs.",items:{type:"string",minLength:1,description:"Single local filesystem path, not a beeos-file:// URI."}}}};const MAX_PATHS=5,REQUEST_TIMEOUT_MS=12e4,MAX_CONCURRENCY=3,BEEOS_FILE_URI_UPLOAD_ERROR="beeos-file:// is already a BeeOS platform file URI, not a local path. Do not upload it; display it directly.";async function runWithConcurrency(e,t){const r=new Array(e.length);let a=0;async function o(){for(;a<e.length;){const t=a++;r[t]=await e[t]()}}const s=[];for(let r=0;r<Math.min(t,e.length);r++)s.push(o());return await Promise.all(s),r}async function validateFiles(e){return Promise.all(e.map(async e=>{try{await access(e,constants.R_OK);return(await stat(e)).isFile()?{path:e,ok:!0}:{path:e,ok:!1,error:"not a regular file"}}catch(t){const r=t.code;let a;return a="ENOENT"===r?"file not found":"ENOTDIR"===r?"path component is not a directory":"EACCES"===r?"permission denied":t instanceof Error?t.message:String(t),{path:e,ok:!1,error:a}}}))}export async function executeUpload(e,t,r,a){const o=parseParams(e);if(!o.ok)return errorResult(o.error);if(!t.agentGatewayUrl)return errorResult("agentGatewayUrl not configured — file upload requires Agent Gateway access");if(!r)return errorResult("agent key pair not available — cannot sign upload requests");const s=t.agentGatewayUrl,i=o.paths,n=(await validateFiles(i)).find(e=>!e.ok);if(n)return errorResult(`${n.path}: ${n.error}`);const l=[],p=i.map(e=>async()=>{try{return await uploadSingleFile(e,s,r,a)}catch(t){if(a?.aborted)throw t;return l.push(`${e}: ${t instanceof Error?t.message:String(t)}`),null}}),c=(await runWithConcurrency(p,3)).filter(e=>null!==e);if(0===c.length)return errorResult(`All uploads failed: ${l.join("; ")}`);const u=c.map(e=>({uri:`beeos-file://${e.fileId}`,name:e.fileName,mimeType:e.mimeType,fileId:e.fileId})),f=c.length===i.length?`Uploaded ${c.length} file(s): ${u.map(e=>e.name).join(", ")}`:`Uploaded ${c.length}/${i.length} file(s): ${u.map(e=>e.name).join(", ")}. Failed: ${l.join("; ")}`,m={ok:!0,files:u};return{...m,content:[{type:"text",text:f},...u.map(e=>({type:"resource_link",uri:e.uri,name:e.name,mimeType:e.mimeType}))],output:m,result:m,details:m,isError:!1}}async function uploadSingleFile(e,t,r,a){const o=await uploadLocalFile(e,t,r,{signal:a,timeoutMs:12e4});return{fileId:o.fileId,downloadUrl:"",fileName:o.name,mimeType:o.mimeType,size:o.size}}function parseParams(e){if(!e||"object"!=typeof e)return{ok:!1,error:"params must be an object"};const t=e;if(Object.keys(t).filter(e=>"paths"!==e).length>0)return{ok:!1,error:"only paths is allowed"};if(!Array.isArray(t.paths))return{ok:!1,error:"paths must be an array"};if(t.paths.length<1)return{ok:!1,error:"paths must contain at least 1 item"};if(t.paths.length>5)return{ok:!1,error:"paths must contain no more than 5 items"};const r=[];for(let e=0;e<t.paths.length;e++){const a=t.paths[e];if("string"!=typeof a||""===a.trim())return{ok:!1,error:`paths[${e}] must be a non-empty string`};const o=a.trim();if(isBeeosFileUri(o))return{ok:!1,error:BEEOS_FILE_URI_UPLOAD_ERROR};r.push(o)}return{ok:!0,paths:r}}function errorResult(e){const t={ok:!1,error:e};return{...t,content:[{type:"text",text:e}],output:t,result:t,details:t,isError:!0}}
@@ -1 +1 @@
1
- import{access,constants,readFile,stat}from"node:fs/promises";import{basename,extname}from"node:path";import{agentFetch}from"../agent-auth.js";const DEFAULT_TIMEOUT_MS=3e4;let _deps=null;export function setUploadDeps(e){_deps=e}export function getUploadDeps(){return _deps}export async function uploadLocalFile(e,t,a,n){await access(e,constants.R_OK);if(!(await stat(e)).isFile())throw new Error("not a regular file");const r=basename(e),s=n?.fileNameOverride??r,i=inferMimeType(s),o=await readFile(e),c=AbortSignal.timeout(n?.timeoutMs??3e4),p=n?.signal?AbortSignal.any([n.signal,c]):c,m=`${t}/api/v1/agent/files`,l=await agentFetch(m+"/presign",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({fileName:s,mimeType:i}),signal:p});if(!l.ok){const e=await l.text().catch(()=>l.statusText);throw new Error(`presign ${l.status}: ${e}`)}const u=await l.json(),d=await fetch(u.uploadUrl,{method:"PUT",headers:{"Content-Type":i},body:new Blob([o],{type:i}),signal:p});if(!d.ok)throw new Error(`S3 PUT failed: ${d.status}`);const f=await agentFetch(m+"/confirm",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({fileId:u.fileId}),signal:p});if(!f.ok){const e=await f.text().catch(()=>f.statusText);throw new Error(`confirm ${f.status}: ${e}`)}const g=await f.json();return{fileId:g.fileId,uri:`beeos-file://${g.fileId}`,name:g.fileName||s,mimeType:g.mimeType||i,size:g.size||o.byteLength}}function inferMimeType(e){switch(extname(e).toLowerCase()){case".txt":return"text/plain";case".md":return"text/markdown";case".json":return"application/json";case".html":return"text/html";case".css":return"text/css";case".js":case".mjs":case".cjs":return"text/javascript";case".ts":return"text/x.typescript";case".tsx":case".jsx":return"text/jsx";case".xml":return"application/xml";case".yaml":case".yml":return"application/yaml";case".toml":return"application/toml";case".pdf":return"application/pdf";case".doc":return"application/msword";case".docx":return"application/vnd.openxmlformats-officedocument.wordprocessingml.document";case".xls":return"application/vnd.ms-excel";case".xlsx":return"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";case".ppt":return"application/vnd.ms-powerpoint";case".pptx":return"application/vnd.openxmlformats-officedocument.presentationml.presentation";case".rtf":return"application/rtf";case".odt":return"application/vnd.oasis.opendocument.text";case".ods":return"application/vnd.oasis.opendocument.spreadsheet";case".odp":return"application/vnd.oasis.opendocument.presentation";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".webp":return"image/webp";case".svg":return"image/svg+xml";case".mp4":return"video/mp4";case".mov":return"video/quicktime";case".webm":return"video/webm";case".mkv":return"video/x-matroska";case".avi":return"video/x-msvideo";case".m4v":return"video/x-m4v";case".mp3":return"audio/mpeg";case".wav":return"audio/wav";case".flac":return"audio/flac";case".aac":return"audio/aac";case".ogg":return"audio/ogg";case".m4a":return"audio/mp4";case".csv":return"text/csv";case".zip":return"application/zip";case".tar":return"application/x-tar";case".gz":case".tgz":return"application/gzip";default:return"application/octet-stream"}}
1
+ import{access,constants,readFile,stat}from"node:fs/promises";import{createHash}from"node:crypto";import{basename,extname,resolve}from"node:path";import{agentFetch}from"../agent-auth.js";import{noteUploadedDeliverable}from"./delivery-ledger.js";const DEFAULT_TIMEOUT_MS=3e4,UPLOAD_REUSE_TTL_MS=18e5,MAX_REUSED_UPLOADS=256,completedUploads=new Map;let _deps=null;export function setUploadDeps(e){_deps=e}export function getUploadDeps(){return _deps}export async function uploadLocalFile(e,t,a,r){await access(e,constants.R_OK);if(!(await stat(e)).isFile())throw new Error("not a regular file");const o=basename(e),s=r?.fileNameOverride??o,n=inferMimeType(s),i=await readFile(e);r?.signal?.throwIfAborted();const c=createHash("sha256").update(i).digest("hex"),p=JSON.stringify([t,Buffer.from(a.publicKey).toString("base64"),resolve(e),s,c]),l=Date.now();for(const[e,t]of completedUploads)t.expiresAt<=l&&completedUploads.delete(e);const d=completedUploads.get(p);if(d)return noteUploadedDeliverable(e),{...d.result};const m=AbortSignal.timeout(r?.timeoutMs??3e4),u=r?.signal?AbortSignal.any([r.signal,m]):m,f=`${t}/api/v1/agent/files`,g=await agentFetch(f+"/presign",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({fileName:s,mimeType:n}),signal:u});if(!g.ok){const e=await g.text().catch(()=>g.statusText);throw new Error(`presign ${g.status}: ${e}`)}const x=await g.json(),w=await fetch(x.uploadUrl,{method:"PUT",headers:{"Content-Type":n},body:new Blob([i],{type:n}),signal:u});if(!w.ok)throw new Error(`S3 PUT failed: ${w.status}`);const v=await agentFetch(f+"/confirm",a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({fileId:x.fileId}),signal:u});if(!v.ok){const e=await v.text().catch(()=>v.statusText);throw new Error(`confirm ${v.status}: ${e}`)}const h=await v.json(),y={fileId:h.fileId,uri:`beeos-file://${h.fileId}`,name:h.fileName||s,mimeType:h.mimeType||n,size:h.size||i.byteLength};if(!y.fileId)throw new Error("confirm returned no fileId");return completedUploads.size>=256&&completedUploads.delete(completedUploads.keys().next().value),completedUploads.set(p,{result:y,expiresAt:Date.now()+18e5}),noteUploadedDeliverable(e),{...y}}function inferMimeType(e){switch(extname(e).toLowerCase()){case".txt":return"text/plain";case".md":return"text/markdown";case".json":return"application/json";case".html":return"text/html";case".css":return"text/css";case".js":case".mjs":case".cjs":return"text/javascript";case".ts":return"text/x.typescript";case".tsx":case".jsx":return"text/jsx";case".xml":return"application/xml";case".yaml":case".yml":return"application/yaml";case".toml":return"application/toml";case".pdf":return"application/pdf";case".doc":return"application/msword";case".docx":return"application/vnd.openxmlformats-officedocument.wordprocessingml.document";case".xls":return"application/vnd.ms-excel";case".xlsx":return"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";case".ppt":return"application/vnd.ms-powerpoint";case".pptx":return"application/vnd.openxmlformats-officedocument.presentationml.presentation";case".rtf":return"application/rtf";case".odt":return"application/vnd.oasis.opendocument.text";case".ods":return"application/vnd.oasis.opendocument.spreadsheet";case".odp":return"application/vnd.oasis.opendocument.presentation";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".webp":return"image/webp";case".svg":return"image/svg+xml";case".mp4":return"video/mp4";case".mov":return"video/quicktime";case".webm":return"video/webm";case".mkv":return"video/x-matroska";case".avi":return"video/x-msvideo";case".m4v":return"video/x-m4v";case".mp3":return"audio/mpeg";case".wav":return"audio/wav";case".flac":return"audio/flac";case".aac":return"audio/aac";case".ogg":return"audio/ogg";case".m4a":return"audio/mp4";case".csv":return"text/csv";case".zip":return"application/zip";case".tar":return"application/x-tar";case".gz":case".tgz":return"application/gzip";default:return"application/octet-stream"}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.6.61",
3
+ "version": "0.6.62-staging.1",
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>",
@@ -87,5 +87,5 @@
87
87
  "fast-uri": ">=3.1.4",
88
88
  "brace-expansion": ">=5.0.9"
89
89
  },
90
- "gitHead": "9e82d663aae89b29c397df8aa20e362029238690"
90
+ "gitHead": "35805fce71ec2f491456f378ca0be5ab8f17218c"
91
91
  }