beeos-claw 0.6.74 → 0.6.75

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
@@ -195,24 +195,80 @@ Connects local OpenClaw agents to the BeeOS platform via the Agent Communication
195
195
 
196
196
  beeos-claw is **only the ACP→v3 protocol translator** for outbound agent replies. All Message Service (MS) Envelope v3 streaming machinery — PATCH batching, snapshot semantics, lazy `MessageClient` connect, open-failure `sendV3` fallback, `stopReason` whitelisting — lives in `@beeos-ai/message-sdk` (`MessageStream` + `MessageClient`).
197
197
 
198
- `src/message-reply-streamer.ts` keeps a `Map<sessionKey, MessageStream>` and per-incoming-update routes:
198
+ Interactive replies use `src/runtime/im-reply-sink.ts`; adopted background runs use
199
+ `src/runtime/message-service-run-output-channel.ts`. Both create and replace
200
+ `tool_use` parts through `toMessagePart` in `src/message-client-types.ts`.
201
+ The same resulting parts are streamed and persisted, so history and reconnect
202
+ use the server's recorded action information.
199
203
 
200
- | ACP `sessionUpdate` | SDK call |
201
- |---|---|
202
- | `agent_message_chunk` | `stream.appendBody(text)` |
203
- | `agent_thought_chunk` | `stream.appendThinking(text)` |
204
- | `tool_call` | `stream.appendToolUse(id, name, args, "streaming")` |
205
- | `tool_call_update` (completed / failed) | `stream.appendToolResult(id, content, isError)` |
206
- | `tool_call_update` content blocks (`resource_link` / `file`) | `stream.addPart({type: "file", ...})` |
204
+ Each `tool_use` includes a server-generated activity:
207
205
 
208
- The only beeos-claw-side concern is the `routeUpdateIntoStream` + extractor helpers at the bottom of `message-reply-streamer.ts`. Anything tied to MS — connect lifecycle, transient PATCH retries, terminal-write fallback when the open POST failed, MS `ValidateStopReason` whitelist — is the SDK's responsibility. Other agent runtimes (device-agent, beeos-claude, …) can drop the same SDK-level wiring in and only need to ship their own ACP-equivalent translator.
206
+ ```json
207
+ {
208
+ "type": "tool_use",
209
+ "id": "call-fonts",
210
+ "name": "exec",
211
+ "arguments": { "command": "fc-list :lang=zh" },
212
+ "state": "streaming",
213
+ "activity": {
214
+ "schema_version": 1,
215
+ "source": "tool_arguments",
216
+ "actions": [{ "kind": "list_chinese_fonts" }]
217
+ }
218
+ }
219
+ ```
209
220
 
210
- See [`@beeos-ai/message-sdk` README](../../sdks/message-sdk-node/README.md#streaming-agent-replies--envelope-v3) for the SDK-side contract.
221
+ Clients translate `kind` into localized wording and use `subject` only when
222
+ present. Supported kinds are `search`, `page`, `read`, `save`, `command`, `other`,
223
+ `find_design_tools`, `find_tools`, `check_rendering`, `list_chinese_fonts`,
224
+ `list_fonts`, `list_files`, `install`, `run_script`, `generate_poster`, and
225
+ `generate_image`. Actions describe the invocation, not its success. A compound
226
+ command can contain multiple actions; unknown shell syntax falls back to
227
+ `command` for the whole invocation. Standard `/bin`, `/usr/bin`, and
228
+ `/usr/local/bin` executable paths use the same rules as bare names. Script
229
+ execution shows the script basename without guessing what it produces.
230
+ `write`/`edit` calls show `save` with the file basename. Unknown tools use `other`.
231
+
232
+ `src/tool-activity.ts` reads actual tool names and arguments, including bounded
233
+ `tool_call` wrappers. It ignores `title`, `tasktitle`, and incoming `activity`.
234
+ Subjects are limited to search queries, URL hostnames, file basenames, and
235
+ allowlisted package names. Raw commands, inline code, URL credentials/query
236
+ strings, and full local paths are not activity subjects. The classifier never
237
+ executes commands and does not alter approval requirements.
238
+
239
+ Tool results and lifecycle events correlate by the explicit canonical call ID.
240
+ Early results wait for that exact card; anonymous summaries are ignored even
241
+ when just one tool is pending. A delayed duplicate cannot finish a different
242
+ call with the same name. Native retained replies persist host-observed results
243
+ by their call ID. Background `pending`/`in_progress` updates keep the tool
244
+ streaming; only `completed`/`failed` updates settle it. Late progress cannot
245
+ replace a terminal result. Error information remains in the matching
246
+ `tool_progress.data.is_error`, separate from activity.
247
+
248
+ Acceptance coverage lives in `message-client-types.test.ts`,
249
+ `runtime/im-reply-sink.test.ts`, and
250
+ `runtime/message-service-run-output-channel.test.ts`: compound command actions,
251
+ misleading titles and prompts, secret-bearing shell/URL arguments, wrapped
252
+ calls, delayed anonymous approval errors, early results for a different ID,
253
+ and approval waiting versus terminal updates.
254
+
255
+ MS transport lifecycle, PATCH retries, fallback writes, and stop reasons remain
256
+ the SDK's responsibility. See the
257
+ [`@beeos-ai/message-sdk` README](../../sdks/message-sdk-node/README.md#streaming-agent-replies--envelope-v3).
211
258
 
212
259
  ### File Transfer
213
260
 
214
261
  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.
215
262
 
263
+ Artifact generation guidance uses one executable per `exec` call so native
264
+ approval can bind the command and its file operands. Discovery uses `ls` / `read`
265
+ or a standalone `which`; shell builtins such as `command -v`, mixed commands,
266
+ redirections and pipelines are not reliable approval requests. A generated script
267
+ is written to a workspace file before a separate interpreter invocation. The
268
+ agent must wait for the real result, verify the artifact, and upload it before
269
+ claiming delivery. Binding errors and pending approvals are never completion,
270
+ and the guidance does not relax the host's approval policy.
271
+
216
272
  The plugin records office/PDF paths produced by `write` / `exec` and uploads any
217
273
  that were not already delivered. Tool-result enrichment attaches those files
218
274
  during the turn; `before_agent_finalize` uploads leftovers without requesting
@@ -1 +1 @@
1
- export const toMessagePart=e=>normalizeJson(e,new WeakSet);const normalizeJson=(e,t)=>{if(null===e||"string"==typeof e||"boolean"==typeof e)return e;if("number"==typeof e)return Number.isFinite(e)?e:null;if("bigint"==typeof e)return e.toString();if(Array.isArray(e)){if(t.has(e))return"[Circular]";t.add(e);const r=e.map(e=>normalizeJson(e,t));return t.delete(e),r}if("object"==typeof e){if(t.has(e))return"[Circular]";t.add(e);const r={};for(const[n,o]of Object.entries(e))void 0!==o&&"function"!=typeof o&&"symbol"!=typeof o&&(r[n]=normalizeJson(o,t));return t.delete(e),r}return String(e)};export const projectedMessages=e=>Object.values(e.getSnapshot().messages);
1
+ import{toolActivity}from"./tool-activity.js";export const toMessagePart=t=>{const e=normalizeJson(t,new WeakSet);return null===e||"object"!=typeof e||Array.isArray(e)||"tool_use"!==e.type||(e.activity=toolActivity(e.name,e.arguments??e.input)),e};const normalizeJson=(t,e)=>{if(null===t||"string"==typeof t||"boolean"==typeof t)return t;if("number"==typeof t)return Number.isFinite(t)?t:null;if("bigint"==typeof t)return t.toString();if(Array.isArray(t)){if(e.has(t))return"[Circular]";e.add(t);const o=t.map(t=>normalizeJson(t,e));return e.delete(t),o}if("object"==typeof t){if(e.has(t))return"[Circular]";e.add(t);const o={};for(const[r,n]of Object.entries(t))void 0!==n&&"function"!=typeof n&&"symbol"!=typeof n&&(o[r]=normalizeJson(n,e));return e.delete(t),o}return String(t)};export const projectedMessages=t=>Object.values(t.getSnapshot().messages);
@@ -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.';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
+ 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- exec: Keep each call to one executable and its arguments. Native approval must bind the exact executable and any script before it can run. Prefer ls/read for inspecting files; for executable discovery use a standalone which call, not the shell builtin command -v. Do not combine discovery, redirections, pipelines, or several commands into one approval request.\n- To generate an artifact with a script, write the script to a workspace file first, then invoke the discovered interpreter with that file as a separate exec call. Wait for the actual execution result, check the output file, and upload it with beeos_upload_file before claiming delivery. Do not claim a local path is a downloadable attachment.\n- An approval binding error means the command was not executed. Explain the failure and use a supported command shape without changing its purpose. Never disable approval, change the execution host to evade it, or treat a pending approval as success. A denied request must stay denied.\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{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}},observedToolResultText=e=>{if(!isPlainRecord(e)||!Array.isArray(e.content))return;const t=e.content.filter(e=>isPlainRecord(e)&&"text"===e.type&&"string"==typeof e.text).map(e=>e.text.trim()).filter(Boolean);return t.length>0?t.join("\n"):void 0};export function createImReplySink(e){const t=e.startedAtMs??Date.now();let r=!1;const s=new Map,n=new Set,o={text:""};let a="",i="",l=!1,d=!1;const u=new Set;let c;const m={receivedSnapshotCount:0,streamingPublishCount:0,terminalPublishCount:0},g=[];let p;const f=new Map,h=new Map,v=[],b=[],y=new Map,I=new Map;let _,S=0,x=1;const k=(e,t={})=>({schema_version:1,seq:e,...t}),q=new Set,$=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)},P=e=>{void 0===e.publishTimer&&(e.publishTimer=setTimeout(()=>{e.publishTimer=void 0,ue("body_preview_flush",()=>{c===e&&(e=>{o.text!==e.targetBody&&(w(e.targetBody),e.streamingPublishCount+=1)})(e)})},200))},T=e=>{const t=c;t?(void 0!==t.publishTimer&&(clearTimeout(t.publishTimer),t.publishTimer=void 0),t.targetBody=e,o.text!==e&&(w(e),t.terminalPublishCount+=1),m.receivedSnapshotCount+=t.receivedSnapshotCount,m.streamingPublishCount+=t.streamingPublishCount,m.terminalPublishCount+=t.terminalPublishCount,c=void 0):w(e)},C=t=>{if(void 0!==t.partIndex)return;const r=S,s=x;e.stream.addPart(toMessagePart({type:"text",text:t.text,state:"done",_seq:s,data:k(s)})),t.partIndex=r,S+=1,x+=1},E=()=>{if(!d){d=!0;for(const e of g)C(e)}},R=e=>{const t={text:e};g.push(t),d&&C(t)},M=(t,r)=>{for(const s of t){const t=I.get(s.url);if(t){const r=mergeSearchSource(t,s);if(r.title===t.title&&r.snippet===t.snippet)continue;const n={...r,partIndex:t.partIndex,displaySeq:t.displaySeq,toolUseId:t.toolUseId};e.stream.replacePart(t.partIndex,toMessagePart({type:"source",url:n.url,...n.title?{title:n.title}:{},...n.snippet?{snippet:n.snippet}:{},tool_use_id:n.toolUseId,_seq:n.displaySeq,data:k(n.displaySeq)})),I.set(s.url,n);continue}const n={...s,partIndex:S,displaySeq:x,toolUseId:r};e.stream.addPart(toMessagePart({type:"source",url:s.url,...s.title?{title:s.title}:{},...s.snippet?{snippet:s.snippet}:{},tool_use_id:r,_seq:n.displaySeq,data:k(n.displaySeq)})),I.set(s.url,n),S+=1,x+=1,ie.source+=1}},O=(e,t,r,s)=>toMessagePart({type:"thinking",text:t,state:r,_seq:e.displaySeq,_started_at_ms:e.startedAtMs,...void 0!==s?{_duration_ms:s}:{},data:k(e.displaySeq,{started_at_ms:e.startedAtMs,...void 0!==s?{duration_ms:s}:{}})}),A=t=>{E();const r={partIndex:S,displaySeq:x,text:t,publishedText:t,receivedSnapshotCount:1,streamingPublishCount:1,startedAtMs:Date.now(),startedAtMonotonicMs:performance.now()};return e.stream.addPart(O(r,t,"streaming")),S+=1,x+=1,p=r,r},F=t=>{void 0===t.publishTimer&&(t.publishTimer=setTimeout(()=>{t.publishTimer=void 0,ue("reasoning_snapshot_flush",()=>{p===t&&(t=>{t.publishedText!==t.text&&(e.stream.replacePart(t.partIndex,O(t,t.text,"streaming")),t.publishedText=t.text,t.streamingPublishCount+=1)})(t)})},200))},N=()=>{const r=p;if(!r)return;void 0!==r.publishTimer&&(clearTimeout(r.publishTimer),r.publishTimer=void 0);const s=Math.max(0,Math.round(performance.now()-r.startedAtMonotonicMs));e.stream.replacePart(r.partIndex,O(r,r.text,"done",s)),p=void 0,logChatLatency(e.logger,"claw.reasoning.settled",e.requestId,t,{reasoning_duration_ms:s,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})},U=e=>{const t=v.indexOf(e);t>=0&&v.splice(t,1)},j=e=>{const t=e?v.filter(t=>f.get(t)?.name===e):v;return 1===t.length?t[0]:void 0},L=e=>{const t=b.map((e,t)=>({hint:e,index:t})).filter(({hint:t})=>!e||t.name===e);if(1!==t.length)return;const[{hint:r,index:s}]=t;return b.splice(s,1),r},B=(e,t)=>{if(h.set(e,t),e===t)return;const r=y.get(e);if(!r)return;const s=Q(t);J(s,r),y.delete(e);const n=f.get(t);n&&(Z(n,s),y.delete(t))},z=e=>h.get(e)??e,D=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),Q=e=>{let t=y.get(e);return t||(t={},y.set(e,t)),t},W=(e,t)=>{if(!e?.length&&!t?.length)return;const r=[],s=new Map;for(const n of[...e??[],...t??[]]){const e=s.get(n.url);void 0===e?(s.set(n.url,r.length),r.push(n)):r[e]=mergeSearchSource(r[e],n)}return r},J=(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.observedResult??=t.observedResult,e.sources=W(e.sources,t.sources)},H=(t,r,s)=>{const n="tool"===t.name&&"tool"!==r?r:t.name,o=!D(t.arguments)&&D(s)?s:t.arguments;n===t.name&&o===t.arguments||(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:n,arguments:o,state:t.state,_seq:t.displaySeq,data:k(t.displaySeq)})),t.name=n,t.arguments=o)},K=(t,r,s,n)=>{const o=f.get(t);if(o)return H(o,r,s),n&&!v.includes(t)&&"streaming"===o.state&&v.push(t),o;E();const a=y.get(t),i="tool"!==r?r:a?.name&&"tool"!==a.name?a.name:r,l=D(s)?s:a?.arguments??s,d=((t,r,s)=>{const n=S,o=x;return e.stream.addPart(toMessagePart({type:"tool_use",id:t,name:r,arguments:s,state:"streaming",_seq:o,data:k(o)})),S+=1,x+=1,{partIndex:n,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 f.set(t,u),(n||u.toolStartObserved||u.itemEventObserved)&&(v.includes(t)||"streaming"!==u.state||v.push(t)),a&&Z(u,a),y.delete(t),u},G=(e,t)=>{const r=(e=>{const t=h.get(e);return t?f.get(t):f.get(e)})(e);if(r)return void 0===t.name&&void 0===t.arguments||H(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 s=Q(e);void 0!==t.name&&(s.name=t.name),void 0!==t.arguments&&(s.arguments=t.arguments),t.toolStartObserved&&(s.toolStartObserved=!0),t.itemEventObserved&&(s.itemEventObserved=!0)},V=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:k(t.displaySeq)})),t.state="done")},X=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(X).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${X(t)}`).join(",")}}`},Y=(t,r,s)=>{const n=`${s?"1":"0"}:${X(r)}`;if(t.progressSignature===n)return;const o=toMessagePart({type:"custom",kind:"tool_progress",data:{tool_use_id:t.id,content:r,...s?{is_error:!0}:{}}});if(void 0===t.progressPartIndex){const r=S;return e.stream.addPart(o),t.progressPartIndex=r,t.progressSignature=n,void(S+=1)}e.stream.replacePart(t.progressPartIndex,o),t.progressSignature=n},Z=(e,t)=>{if(t.progress?.length)for(const r of t.progress)Y(e,r.content,r.isError);t.observedResult&&(Y(e,t.observedResult.content,t.observedResult.isError),e.observedResult=t.observedResult),(t.progress?.length||t.observedResult)&&(V(e),U(e.id)),t.sources?.length&&M(t.sources,e.id)},ee=e=>[...new Set([e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e)).map(e=>e.trim()).filter(Boolean))],te=(e,t=[])=>{const r=[...new Set([...ee(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.":""},re=t=>{for(const r of t)e.stream.addPart(toMessagePart({type:"resource_link",uri:r.uri,name:r.name,...r.mimeType?{mimeType:r.mimeType}:{}})),S+=1},se=t=>{q.add(t);const r=()=>{q.delete(t)};e.onCallbackFailure?t.then(r,r):t.finally(r)},ne=(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 s=pickRawResultFromPayload(r),n=(async()=>{let n=[];try{n=await resolveToolArtifactLinks({toolCallId:t.id,toolName:t.name,rawResult:s,rawText:"string"==typeof r.text?r.text:void 0,params:asToolParams(t.arguments),cwd:sessionWorkspaceDir(e.sessionKey),payloadMediaUrls:ee(r)})}catch(r){const s=r instanceof Error?r.message:String(r);if(e.logger.warn?.(`[im-sink] resolveToolArtifactLinks failed requestId=${e.requestId} toolUseId=${t.id} error=${s}`),e.onCallbackFailure?.(r),e.onCallbackFailure)throw r;return}0!==n.length?(e.logger.info?.(`[im-sink] tool artifact enrichment resolved ${n.length} link(s) requestId=${e.requestId} toolUseId=${t.id}`),await new Promise((s,o)=>{!1===e.callbackQueue.enqueueSync(()=>{try{if(e.abortSignal.aborted||e.stream.isTerminated)return;const s=n.map(e=>e.uri);t.artifactMediaUrls=s;const o=t.observedResult;Y(t,te(o?{text:o.content,isError:o.isError}:r,s),o?.isError??!0===r.isError),((t,r,s)=>{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}:{}})),s),S+=1,re(r))})(t,n,o?.isError??!0===r.isError)}catch(r){const s=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool artifact enrichment apply failed requestId=${e.requestId} toolUseId=${t.id} error=${s}`),e.onCallbackFailure?.(r),e.onCallbackFailure&&o(r)}finally{s()}})&&e.onCallbackFailure&&e.callbackQueue.waitForIdle().then(s,o)})):e.logger.debug?.(`[im-sink] tool artifact enrichment found no links requestId=${e.requestId} toolUseId=${t.id} name=${t.name}`)})().catch(r=>{const s=r instanceof Error?r.message:String(r);if(e.logger.warn?.(`[im-sink] tool artifact enrichment failed requestId=${e.requestId} toolUseId=${t.id} error=${s}`),e.onCallbackFailure?.(r),e.onCallbackFailure)throw r});se(n)},oe=(e,t,r)=>{const s=h.get(e);if(s)return G(s,{name:t,arguments:r});const n=G(e,{name:t,arguments:r});if(n)return n;const o=j(t);if(o)return B(e,o),G(o,{name:t,arguments:r});G(e,{name:t,arguments:r})},ae=(e,t)=>{const r=h.get(e);if(r)return G(r,{name:t,itemEventObserved:!0,pending:!0});const s=G(e,{name:t,itemEventObserved:!0,pending:!0});if(s)return s;const n=(e=>{const t=v.filter(t=>{const r=f.get(t);return!(void 0===r||e&&r.name!==e||r.itemEventObserved)});return 1===t.length?t[0]:void 0})(t);if(n)return B(e,n),G(n,{name:t,itemEventObserved:!0,pending:!0});const o=L(t);G(e,{name:t,arguments:o?.arguments,itemEventObserved:!0,pending:!0})},ie={tool:0,block:0,final:0,source:0};let le,de=!1;const ue=(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):!de||(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 s=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=${s}`),e.onCallbackFailure?.(r)}})},ce={sendToolResult:t=>(ue("tool_progress",()=>{const r=resolveRuntimeToolCallId(t),s=resolveRuntimeToolName(t),n=resolveRuntimeToolArguments(t),o=te(t),a=!0===t.isError;let i;if(r){if(i=oe(r,s,n),!i){const e=Q(r);return s&&(e.name=s),D(n)&&(e.arguments=n),e.progress=[...e.progress??[],{content:o,isError:a}],void(ie.tool+=1)}}else{const t=s?j(s):j();if(!t)return L(s),void e.logger.debug?.(`[im-sink] unattributed tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${s??"unknown"} pending=${v.length}`);i=G(t,{name:s,arguments:n})}i&&(ie.tool+=1,i.observedResult||Y(i,o,a),V(i),U(i.id),ne(i,t))}),!0),sendBlockReply:e=>(ue("block_reply",()=>{ie.block+=1,isNonEmptyString(e.text)&&(le=!0!==e.isError?void 0:e.text)}),!0),sendFinalReply:t=>(ue("final_reply",()=>{var r;(ie.final+=1,!0!==t.isError)?((t=>{const r=ee(t).filter(e=>!$.has(e));if(0===r.length)return;for(const e of r)$.add(e);const s=(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)$.delete(e);if(e.logger.warn?.(`[im-sink] final media enrichment failed requestId=${e.requestId} error=${t instanceof Error?t.message:String(t)}`),e.onCallbackFailure?.(t),e.onCallbackFailure)throw t;return}if(0!==t.length)await new Promise((r,s)=>{!1===e.callbackQueue.enqueueSync(()=>{try{if(e.abortSignal.aborted||e.stream.isTerminated)return;re(t)}catch(t){e.logger.warn?.(`[im-sink] final media enrichment apply failed requestId=${e.requestId} error=${t instanceof Error?t.message:String(t)}`),e.onCallbackFailure?.(t),e.onCallbackFailure&&s(t)}finally{r()}})&&e.onCallbackFailure&&e.callbackQueue.waitForIdle().then(r,s)});else for(const e of r)$.delete(e)})();se(s)})(t),isNonEmptyString(t.text)&&(le=void 0,l||(r=t.text,i="",a+=r,T(a),R(r),l=!0))):le=t.text}),!0),waitForIdle:async()=>{for(let t=0;(e.onCallbackFailure||t<16)&&(await e.callbackQueue.waitForIdle(),0!==q.size);t++)e.onCallbackFailure?await Promise.all([...q]):await Promise.allSettled([...q]);await e.callbackQueue.waitForIdle();const t=takeFinalizeAttachments(e.sessionKey);t.length>0&&!e.abortSignal.aborted&&!e.stream.isTerminated&&re(t)},getQueuedCounts:()=>({tool:ie.tool,block:ie.block,final:ie.final}),getAccumulatedBody:()=>o.text,getTerminalErrorText:()=>le,markComplete:r=>{N(),(b.length>0||y.size>0)&&e.logger.debug?.(`[im-sink] dropping uncommitted lifecycle tool signals requestId=${e.requestId} legacy_hints=${b.length} buffered=${y.size}`),i="",T(a),m.receivedSnapshotCount>0&&logChatLatency(e.logger,"claw.text_preview.settled",e.requestId,t,{text_preview_snapshot_count:m.receivedSnapshotCount,text_preview_streaming_publish_count:m.streamingPublishCount,text_preview_terminal_publish_count:m.terminalPublishCount,text_preview_coalesced_snapshot_count:Math.max(0,m.receivedSnapshotCount-m.streamingPublishCount-m.terminalPublishCount),text_preview_publish_interval_ms:200});for(const t of f.values())try{"success"===r&&isUploadArtifactToolName(t.name)&&!t.durableUploadFilesEmitted&&ne(t,{toolCallId:t.id,toolName:t.name}),"success"===r&&V(t),U(t.id)}catch(r){const s=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${t.id} error=${s}`)}b.length=0,y.clear(),de=!0}},me={...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:s=>{ue("partial",()=>{"string"==typeof s.text&&(!r&&s.text.length>0&&(r=!0,logChatLatency(e.logger,"claw.model.first_delta",e.requestId,t)),i=s.text,(e=>{const t=c;if(!t){if(o.text===e)return;return w(e),void(c={targetBody:e,receivedSnapshotCount:1,streamingPublishCount:1,terminalPublishCount:0})}t.targetBody!==e&&(t.targetBody=e,t.receivedSnapshotCount+=1,P(t))})(a+i))})},onReasoningStream:e=>{ue("reasoning",()=>{if(!isNonEmptyString(e.text))return;const t=e.text,r=p??A(t);t!==r.text&&(r.text.startsWith(t)||(t.startsWith(r.text)?(r.text=t,r.receivedSnapshotCount+=1,F(r)):(N(),A(t))))})},onReasoningEnd:()=>{ue("reasoning_end",()=>{N()})},onAssistantMessageStart:()=>{ue("assistant_message_start",()=>{i="",l=!1,T(a),e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)})},onToolStart:r=>{ue("tool_start",()=>{const n="string"==typeof r.phase?r.phase:"start",o=resolveRuntimeToolName(r)??"tool",a=resolveRuntimeToolCallId(r),i=resolveRuntimeToolArguments(r);if("update"===n)return void(a&&G(z(a),{name:o,arguments:i,pending:!0}));if(a&&s.set(a,Date.now()),logChatLatency(e.logger,"claw.tool.start",e.requestId,t,{tool:o}),a){G(a,{name:o,arguments:i,toolStartObserved:!0,pending:!0});const e=j(o);return void(e&&e!==a&&!h.has(a)&&(B(a,e),G(e,{name:o,arguments:i,toolStartObserved:!0,pending:!0})))}const l=(d=o,v.map(e=>f.get(e)).filter(e=>void 0!==e&&(!d||e.name===d)&&e.itemEventObserved));var d;if(1===l.length)return void G(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=[...y.entries()].filter(([,e])=>!e.name||e.name===o||"tool"===e.name);1!==u.length?b.push({name:o,arguments:i}):G(u[0][0],{name:o,arguments:i,toolStartObserved:!0,pending:!0})})},onItemEvent:e=>{ue("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",s=resolveRuntimeToolCallId(e)??("string"==typeof e.itemId&&e.itemId.trim()?e.itemId.trim():void 0),n=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);s&&n&&("update"!==r?ae(s,n):G(z(s),{name:n,itemEventObserved:!0,pending:!0}))})}},ge={add(t){if(e.stream.isTerminated)return;const r=S;return e.stream.addPart(t),S+=1,r}},pe={add:e=>ge.add(e),replace:(t,r)=>!e.stream.isTerminated&&(e.stream.replacePart(t,r),!0)};return{dispatcher:ce,replyOptions:me,commitAssistantMessage:t=>{ue("assistant_message_commit",()=>{if(u.has(t.key))return;u.add(t.key),l=!0,le=void 0,i="";const r=t.segments.filter(e=>"text"===e.type).map(e=>e.text).join("");a+=r,T(a);for(const e of t.segments)"text"!==e.type?K(e.id,e.name,e.arguments,!0):R(e.text);if(void 0!==t.executedModel||void 0!==_){const r=toMessagePart({type:"custom",kind:"execution_model",data:{executed_model:t.executedModel??""}});void 0===_?(e.stream.addPart(r),_=S++):e.stream.replacePart(_,r)}})},observeToolResult:r=>{ue("tool_result_observation",()=>{const o=r.toolCallId.trim(),a=r.toolName.trim();if(!o||!a)return;const i=!n.has(o);if(i){n.add(o);const i=s.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"}:{}})}if(e.persistObservedToolResults&&i){const e=observedToolResultText(r.rawResult);if(void 0!==e){const t=oe(o,a);if(t)Y(t,te({text:e,isError:r.isError},t.artifactMediaUrls),r.isError),t.observedResult={content:e,isError:r.isError},V(t),U(t.id);else{const t=Q(z(o));t.name??=a,t.observedResult={content:e,isError:r.isError}}}}const l=searchSourceProvider(r.rawResult)??"unknown";let d=0;try{const t=extractSearchSources({toolName:a,rawResult:r.rawResult,isError:r.isError});if(d=t.length,0===d)return;const s=oe(o,a);if(s)M(t,s.id);else{const e=Q(z(o));e.name??=a,e.sources=W(e.sources,t)}e.logger.debug?.(`[im-sink] search source projection requestId=${e.requestId} tool=${a} provider=${l} source_count=${d}`)}catch{e.logger.warn?.(`[im-sink] search source projection ignored requestId=${e.requestId} tool=${a} provider=${l} source_count=${d}`)}})},getSourceCount:()=>ie.source,approvalParts:ge,questionParts:pe}}
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}},observedToolResultText=e=>{if(!isPlainRecord(e)||!Array.isArray(e.content))return;const t=e.content.filter(e=>isPlainRecord(e)&&"text"===e.type&&"string"==typeof e.text).map(e=>e.text.trim()).filter(Boolean);return t.length>0?t.join("\n"):void 0};export function createImReplySink(e){const t=e.startedAtMs??Date.now();let r=!1;const o=new Map,s=new Set,n={text:""};let a="",i="",l=!1,d=!1;const u=new Set;let c;const m={receivedSnapshotCount:0,streamingPublishCount:0,terminalPublishCount:0},g=[];let p;const f=new Map,h=[],b=new Map,y=new Map;let v,I=0,_=1;const S=(e,t={})=>({schema_version:1,seq:e,...t}),x=new Set,k=new Set,q=t=>{t!==n.text&&(t.startsWith(n.text)?e.stream.appendBody(t.slice(n.text.length)):e.stream.setBody(t),n.text=t)},w=e=>{void 0===e.publishTimer&&(e.publishTimer=setTimeout(()=>{e.publishTimer=void 0,te("body_preview_flush",()=>{c===e&&(e=>{n.text!==e.targetBody&&(q(e.targetBody),e.streamingPublishCount+=1)})(e)})},200))},$=e=>{const t=c;t?(void 0!==t.publishTimer&&(clearTimeout(t.publishTimer),t.publishTimer=void 0),t.targetBody=e,n.text!==e&&(q(e),t.terminalPublishCount+=1),m.receivedSnapshotCount+=t.receivedSnapshotCount,m.streamingPublishCount+=t.streamingPublishCount,m.terminalPublishCount+=t.terminalPublishCount,c=void 0):q(e)},P=t=>{if(void 0!==t.partIndex)return;const r=I,o=_;e.stream.addPart(toMessagePart({type:"text",text:t.text,state:"done",_seq:o,data:S(o)})),t.partIndex=r,I+=1,_+=1},T=()=>{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 o of t){const t=y.get(o.url);if(t){const r=mergeSearchSource(t,o);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:S(s.displaySeq)})),y.set(o.url,s);continue}const s={...o,partIndex:I,displaySeq:_,toolUseId:r};e.stream.addPart(toMessagePart({type:"source",url:o.url,...o.title?{title:o.title}:{},...o.snippet?{snippet:o.snippet}:{},tool_use_id:r,_seq:s.displaySeq,data:S(s.displaySeq)})),y.set(o.url,s),I+=1,_+=1,Y.source+=1}},E=(e,t,r,o)=>toMessagePart({type:"thinking",text:t,state:r,_seq:e.displaySeq,_started_at_ms:e.startedAtMs,...void 0!==o?{_duration_ms:o}:{},data:S(e.displaySeq,{started_at_ms:e.startedAtMs,...void 0!==o?{duration_ms:o}:{}})}),M=t=>{T();const r={partIndex:I,displaySeq:_,text:t,publishedText:t,receivedSnapshotCount:1,streamingPublishCount:1,startedAtMs:Date.now(),startedAtMonotonicMs:performance.now()};return e.stream.addPart(E(r,t,"streaming")),I+=1,_+=1,p=r,r},A=t=>{void 0===t.publishTimer&&(t.publishTimer=setTimeout(()=>{t.publishTimer=void 0,te("reasoning_snapshot_flush",()=>{p===t&&(t=>{t.publishedText!==t.text&&(e.stream.replacePart(t.partIndex,E(t,t.text,"streaming")),t.publishedText=t.text,t.streamingPublishCount+=1)})(t)})},200))},F=()=>{const r=p;if(!r)return;void 0!==r.publishTimer&&(clearTimeout(r.publishTimer),r.publishTimer=void 0);const o=Math.max(0,Math.round(performance.now()-r.startedAtMonotonicMs));e.stream.replacePart(r.partIndex,E(r,r.text,"done",o)),p=void 0,logChatLatency(e.logger,"claw.reasoning.settled",e.requestId,t,{reasoning_duration_ms:o,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})},O=e=>{const t=h.indexOf(e);t>=0&&h.splice(t,1)},N=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),U=e=>{let t=b.get(e);return t||(t={},b.set(e,t)),t},j=(t,r,o)=>{const s="tool"===t.name&&"tool"!==r?r:t.name,n=!N(t.arguments)&&N(o)?o:t.arguments;s===t.name&&n===t.arguments||(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:s,arguments:n,state:t.state,_seq:t.displaySeq,data:S(t.displaySeq)})),t.name=s,t.arguments=n)},L=(t,r,o,s)=>{const n=f.get(t);if(n)return j(n,r,o),s&&!h.includes(t)&&"streaming"===n.state&&h.push(t),n;T();const a=b.get(t),i="tool"!==r?r:a?.name&&"tool"!==a.name?a.name:r,l=N(o)?o:a?.arguments??o,d=((t,r,o)=>{const s=I,n=_;return e.stream.addPart(toMessagePart({type:"tool_use",id:t,name:r,arguments:o,state:"streaming",_seq:n,data:S(n)})),I+=1,_+=1,{partIndex:s,displaySeq:n}})(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 f.set(t,u),(s||u.toolStartObserved||u.itemEventObserved)&&(h.includes(t)||"streaming"!==u.state||h.push(t)),a&&W(u,a),b.delete(t),u},B=(e,t)=>{const r=(e=>f.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)&&!h.includes(r.id)&&"streaming"===r.state&&h.push(r.id),r;const o=U(e);void 0!==t.name&&(o.name=t.name),void 0!==t.arguments&&(o.arguments=t.arguments),t.toolStartObserved&&(o.toolStartObserved=!0),t.itemEventObserved&&(o.itemEventObserved=!0)},z=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:S(t.displaySeq)})),t.state="done")},D=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(D).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${D(t)}`).join(",")}}`},Q=(t,r,o)=>{const s=`${o?"1":"0"}:${D(r)}`;if(t.progressSignature===s)return;const n=toMessagePart({type:"custom",kind:"tool_progress",data:{tool_use_id:t.id,content:r,...o?{is_error:!0}:{}}});if(void 0===t.progressPartIndex){const r=I;return e.stream.addPart(n),t.progressPartIndex=r,t.progressSignature=s,void(I+=1)}e.stream.replacePart(t.progressPartIndex,n),t.progressSignature=s},W=(e,t)=>{if(t.progress?.length)for(const r of t.progress)Q(e,r.content,r.isError);t.observedResult&&(Q(e,t.observedResult.content,t.observedResult.isError),e.observedResult=t.observedResult),(t.progress?.length||t.observedResult)&&(z(e),O(e.id)),t.sources?.length&&R(t.sources,e.id)},J=e=>[...new Set([e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e)).map(e=>e.trim()).filter(Boolean))],H=(e,t=[])=>{const r=[...new Set([...J(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.":""},K=t=>{for(const r of t)e.stream.addPart(toMessagePart({type:"resource_link",uri:r.uri,name:r.name,...r.mimeType?{mimeType:r.mimeType}:{}})),I+=1},G=t=>{x.add(t);const r=()=>{x.delete(t)};e.onCallbackFailure?t.then(r,r):t.finally(r)},V=(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 o=pickRawResultFromPayload(r),s=(async()=>{let s=[];try{s=await resolveToolArtifactLinks({toolCallId:t.id,toolName:t.name,rawResult:o,rawText:"string"==typeof r.text?r.text:void 0,params:asToolParams(t.arguments),cwd:sessionWorkspaceDir(e.sessionKey),payloadMediaUrls:J(r)})}catch(r){const o=r instanceof Error?r.message:String(r);if(e.logger.warn?.(`[im-sink] resolveToolArtifactLinks failed requestId=${e.requestId} toolUseId=${t.id} error=${o}`),e.onCallbackFailure?.(r),e.onCallbackFailure)throw r;return}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((o,n)=>{!1===e.callbackQueue.enqueueSync(()=>{try{if(e.abortSignal.aborted||e.stream.isTerminated)return;const o=s.map(e=>e.uri);t.artifactMediaUrls=o;const n=t.observedResult;Q(t,H(n?{text:n.content,isError:n.isError}:r,o),n?.isError??!0===r.isError),((t,r,o)=>{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}:{}})),o),I+=1,K(r))})(t,s,n?.isError??!0===r.isError)}catch(r){const o=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool artifact enrichment apply failed requestId=${e.requestId} toolUseId=${t.id} error=${o}`),e.onCallbackFailure?.(r),e.onCallbackFailure&&n(r)}finally{o()}})&&e.onCallbackFailure&&e.callbackQueue.waitForIdle().then(o,n)})):e.logger.debug?.(`[im-sink] tool artifact enrichment found no links requestId=${e.requestId} toolUseId=${t.id} name=${t.name}`)})().catch(r=>{const o=r instanceof Error?r.message:String(r);if(e.logger.warn?.(`[im-sink] tool artifact enrichment failed requestId=${e.requestId} toolUseId=${t.id} error=${o}`),e.onCallbackFailure?.(r),e.onCallbackFailure)throw r});G(s)},X=(e,t,r)=>B(e,{name:t,arguments:r}),Y={tool:0,block:0,final:0,source:0};let Z,ee=!1;const te=(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):!ee||(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 o=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=${o}`),e.onCallbackFailure?.(r)}})},re={sendToolResult:t=>(te("tool_progress",()=>{const r=resolveRuntimeToolCallId(t),o=resolveRuntimeToolName(t),s=resolveRuntimeToolArguments(t),n=H(t),a=!0===t.isError;let i;if(r){if(i=X(r,o,s),!i){const e=U(r);return o&&(e.name=o),N(s)&&(e.arguments=s),e.progress=[...e.progress??[],{content:n,isError:a}],void(Y.tool+=1)}i&&(Y.tool+=1,i.observedResult||Q(i,n,a),z(i),O(i.id),V(i,t))}else e.logger.debug?.(`[im-sink] unattributed tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${o??"unknown"} pending=${h.length}`)}),!0),sendBlockReply:e=>(te("block_reply",()=>{Y.block+=1,isNonEmptyString(e.text)&&(Z=!0!==e.isError?void 0:e.text)}),!0),sendFinalReply:t=>(te("final_reply",()=>{var r;(Y.final+=1,!0!==t.isError)?((t=>{const r=J(t).filter(e=>!k.has(e));if(0===r.length)return;for(const e of r)k.add(e);const o=(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)k.delete(e);if(e.logger.warn?.(`[im-sink] final media enrichment failed requestId=${e.requestId} error=${t instanceof Error?t.message:String(t)}`),e.onCallbackFailure?.(t),e.onCallbackFailure)throw t;return}if(0!==t.length)await new Promise((r,o)=>{!1===e.callbackQueue.enqueueSync(()=>{try{if(e.abortSignal.aborted||e.stream.isTerminated)return;K(t)}catch(t){e.logger.warn?.(`[im-sink] final media enrichment apply failed requestId=${e.requestId} error=${t instanceof Error?t.message:String(t)}`),e.onCallbackFailure?.(t),e.onCallbackFailure&&o(t)}finally{r()}})&&e.onCallbackFailure&&e.callbackQueue.waitForIdle().then(r,o)});else for(const e of r)k.delete(e)})();G(o)})(t),isNonEmptyString(t.text)&&(Z=void 0,l||(r=t.text,i="",a+=r,$(a),C(r),l=!0))):Z=t.text}),!0),waitForIdle:async()=>{for(let t=0;(e.onCallbackFailure||t<16)&&(await e.callbackQueue.waitForIdle(),0!==x.size);t++)e.onCallbackFailure?await Promise.all([...x]):await Promise.allSettled([...x]);await e.callbackQueue.waitForIdle();const t=takeFinalizeAttachments(e.sessionKey);t.length>0&&!e.abortSignal.aborted&&!e.stream.isTerminated&&K(t)},getQueuedCounts:()=>({tool:Y.tool,block:Y.block,final:Y.final}),getAccumulatedBody:()=>n.text,getTerminalErrorText:()=>Z,markComplete:r=>{F(),b.size>0&&e.logger.debug?.(`[im-sink] dropping uncommitted lifecycle tool signals requestId=${e.requestId} buffered=${b.size}`),i="",$(a),m.receivedSnapshotCount>0&&logChatLatency(e.logger,"claw.text_preview.settled",e.requestId,t,{text_preview_snapshot_count:m.receivedSnapshotCount,text_preview_streaming_publish_count:m.streamingPublishCount,text_preview_terminal_publish_count:m.terminalPublishCount,text_preview_coalesced_snapshot_count:Math.max(0,m.receivedSnapshotCount-m.streamingPublishCount-m.terminalPublishCount),text_preview_publish_interval_ms:200});for(const t of f.values())try{"success"===r&&isUploadArtifactToolName(t.name)&&!t.durableUploadFilesEmitted&&V(t,{toolCallId:t.id,toolName:t.name}),"success"===r&&z(t),O(t.id)}catch(r){const o=r instanceof Error?r.message:String(r);e.logger.warn?.(`[im-sink] tool finalization failed requestId=${e.requestId} toolUseId=${t.id} error=${o}`)}b.clear(),ee=!0}},oe={...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:o=>{te("partial",()=>{"string"==typeof o.text&&(!r&&o.text.length>0&&(r=!0,logChatLatency(e.logger,"claw.model.first_delta",e.requestId,t)),i=o.text,(e=>{const t=c;if(!t){if(n.text===e)return;return q(e),void(c={targetBody:e,receivedSnapshotCount:1,streamingPublishCount:1,terminalPublishCount:0})}t.targetBody!==e&&(t.targetBody=e,t.receivedSnapshotCount+=1,w(t))})(a+i))})},onReasoningStream:e=>{te("reasoning",()=>{if(!isNonEmptyString(e.text))return;const t=e.text,r=p??M(t);t!==r.text&&(r.text.startsWith(t)||(t.startsWith(r.text)?(r.text=t,r.receivedSnapshotCount+=1,A(r)):(F(),M(t))))})},onReasoningEnd:()=>{te("reasoning_end",()=>{F()})},onAssistantMessageStart:()=>{te("assistant_message_start",()=>{i="",l=!1,$(a),e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)})},onToolStart:r=>{te("tool_start",()=>{const s="string"==typeof r.phase?r.phase:"start",n=resolveRuntimeToolName(r)??"tool",a=resolveRuntimeToolCallId(r),i=resolveRuntimeToolArguments(r);"update"!==s?(a&&o.set(a,Date.now()),logChatLatency(e.logger,"claw.tool.start",e.requestId,t,{tool:n}),a&&B(a,{name:n,arguments:i,toolStartObserved:!0,pending:!0})):a&&B(a,{name:n,arguments:i,pending:!0})})},onItemEvent:e=>{te("item_event",()=>{const t="string"==typeof e.kind?e.kind.toLowerCase():"";if(t&&"tool"!==t)return;"string"==typeof e.phase&&e.phase.toLowerCase();const r=resolveRuntimeToolCallId(e)??("string"==typeof e.itemId&&e.itemId.trim()?e.itemId.trim():void 0),o=resolveRuntimeToolName(e)??("string"==typeof e.title&&e.title.trim()?e.title.trim():void 0);r&&o&&B(r,{name:o,itemEventObserved:!0,pending:!0})})}},se={add(t){if(e.stream.isTerminated)return;const r=I;return e.stream.addPart(t),I+=1,r}},ne={add:e=>se.add(e),replace:(t,r)=>!e.stream.isTerminated&&(e.stream.replacePart(t,r),!0)};return{dispatcher:re,replyOptions:oe,commitAssistantMessage:t=>{te("assistant_message_commit",()=>{if(u.has(t.key))return;u.add(t.key),l=!0,Z=void 0,i="";const r=t.segments.filter(e=>"text"===e.type).map(e=>e.text).join("");a+=r,$(a);for(const e of t.segments)"text"!==e.type?L(e.id,e.name,e.arguments,!0):C(e.text);if(void 0!==t.executedModel||void 0!==v){const r=toMessagePart({type:"custom",kind:"execution_model",data:{executed_model:t.executedModel??""}});void 0===v?(e.stream.addPart(r),v=I++):e.stream.replacePart(v,r)}})},observeToolResult:r=>{te("tool_result_observation",()=>{const n=r.toolCallId.trim(),a=r.toolName.trim();if(!n||!a)return;const i=!s.has(n);if(i){s.add(n);const i=o.get(n);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"}:{}})}if(e.persistObservedToolResults&&i){const e=observedToolResultText(r.rawResult);if(void 0!==e){const t=X(n,a);if(t)Q(t,H({text:e,isError:r.isError},t.artifactMediaUrls),r.isError),t.observedResult={content:e,isError:r.isError},z(t),O(t.id);else{const t=U(n);t.name??=a,t.observedResult={content:e,isError:r.isError}}}}const l=searchSourceProvider(r.rawResult)??"unknown";let d=0;try{const t=extractSearchSources({toolName:a,rawResult:r.rawResult,isError:r.isError});if(d=t.length,0===d)return;const o=X(n,a);if(o)R(t,o.id);else{const e=U(n);e.name??=a,e.sources=((e,t)=>{if(!e?.length&&!t?.length)return;const r=[],o=new Map;for(const s of[...e??[],...t??[]]){const e=o.get(s.url);void 0===e?(o.set(s.url,r.length),r.push(s)):r[e]=mergeSearchSource(r[e],s)}return r})(e.sources,t)}e.logger.debug?.(`[im-sink] search source projection requestId=${e.requestId} tool=${a} provider=${l} source_count=${d}`)}catch{e.logger.warn?.(`[im-sink] search source projection ignored requestId=${e.requestId} tool=${a} provider=${l} source_count=${d}`)}})},getSourceCount:()=>Y.source,approvalParts:se,questionParts:ne}}
@@ -1 +1 @@
1
- import{dedupeMessageServiceArtifacts,extractMessageToolCaption,messageServiceFileParts,messageServiceRunMessageId,sanitizeMessageServiceText}from"./message-service-output-format.js";import{toMessagePart}from"../message-client-types.js";import{extractSearchSources,mergeSearchSource,searchSourceProvider}from"./search-source-extractor.js";import{toMessageServiceCompletedStopReason}from"./message-service-stop-reason.js";import{messageStopReasonForRuntimeFailure}from"./im-runtime-failure-policy.js";import{writeRuntimeFailure}from"./write-runtime-failure.js";export class MessageServiceRunOutputChannel{deps;id="message_service";runs=new Map;constructor(e){this.deps=e}open(e){if(!e.conversationId)throw new Error(`Message Service run has no conversation: ${e.runId}`);this.runs.set(e.runId,{assistantText:"",reasoningText:"",artifacts:[],artifactUris:new Set,tools:[],toolsById:new Map,sourcesByUrl:new Map})}project(e,t){const r=this.state(e.runId);try{switch(t.type){case"assistant_delta":this.projectAssistantDelta(e,r,t.content);break;case"thought_chunk":this.projectThoughtDelta(e,r,t.content);break;case"tool_call":this.projectToolStart(e,r,t);break;case"tool_call_update":this.projectToolUpdate(e,r,t)}}catch(t){throw this.reportProjectionFailure(e,t,"project"),t}}async terminal(e,t,r){const s=this.state(e.runId);try{const o=dedupeMessageServiceArtifacts(s.artifacts);let n,a=sanitizeMessageServiceText(s.assistantText)||sanitizeMessageServiceText(s.caption);r.expectedUploadArtifacts&&0===o.length?(a="The generated attachment could not be delivered.",n="attachment_delivery_failed"):"failed"===t.kind&&(a||=t.message,n="interrupted"===t.mode?t.errorCode:"error");const i="failed"===r.persistenceReconciliation||"timed_out"===r.persistenceReconciliation;!i||a||0!==o.length||0!==s.tools.length||s.reasoningText||"complete"!==t.kind||(a="The response could not be delivered.",n=`persistence_reconciliation_${r.persistenceReconciliation}`),i&&this.deps.writeObs({component:"message-service-run-output",domain:"lifecycle",name:"timed_out"===r.persistenceReconciliation?"run_output.reconciliation_timeout":"run_output.reconciliation_failed",severity:"warn",payload:{conversationId:e.conversationId,runId:e.runId,status:r.persistenceReconciliation}});if(!(Boolean(a)||o.length>0||s.tools.length>0||Boolean(s.reasoningText))&&"complete"===t.kind)return;const c=this.stream(e,s);if(a!==s.assistantText&&c.setBody(a),this.finishReasoning(s),this.finishTools(s),"cancelled"===t.kind)await c.cancel({stopReason:"user_stop"});else if("attachment_delivery_failed"===n||n?.startsWith("persistence_reconciliation_"))await c.fail({stopReason:n});else if("failed"===t.kind)await writeRuntimeFailure(c,{body:a??"",stopReason:t.classification?messageStopReasonForRuntimeFailure(t.classification):"error",businessErrorCode:t.classification?.businessErrorCode});else{const e="complete"===t.kind?toMessageServiceCompletedStopReason(t.stopReason):"end_turn";await c.finalize({stopReason:e})}this.deps.writeObs({component:"message-service-run-output",domain:"lifecycle",name:n?"run_output.failed":"run_output.completed",severity:n?"warn":"info",payload:{conversationId:e.conversationId,runId:e.runId,textLen:a.length,artifactCount:o.length,toolCount:s.tools.length,sourceCount:s.sourcesByUrl.size}})}catch(t){throw this.reportProjectionFailure(e,t,"terminal"),t}finally{this.runs.delete(e.runId)}}projectAssistantDelta(e,t,r){const s=asRecord(r);if(!s)return;if("text"===s.type&&"string"==typeof s.text)return t.assistantText+=s.text,void this.stream(e,t).appendBody(s.text);const o=artifactFromBlock(s);o&&this.projectArtifact(e,t,o)}projectThoughtDelta(e,t,r){const s=asRecord(r);if("text"!==s?.type||"string"!=typeof s.text)return;t.reasoningText+=s.text;const o=this.stream(e,t),n=toMessagePart({type:"thinking",text:t.reasoningText,state:"streaming"});void 0===t.reasoningPartIndex?(t.reasoningPartIndex=o.parts.length,o.addPart(n)):o.replacePart(t.reasoningPartIndex,n)}projectToolStart(e,t,r){const s={id:String(r.toolCallId),name:r.name??r.title,arguments:r.rawInput??parseArguments(r.arguments),isError:!1,completed:!1};t.tools.push(s),t.toolsById.set(s.id,s),this.upsertToolUse(e,t,s);const o=extractMessageToolCaption(s.name,s.arguments);o&&(t.caption=o)}projectToolUpdate(e,t,r){const s=String(r.toolCallId);let o=t.toolsById.get(s);o||(o={id:s,name:r.name??r.title??"tool",arguments:{},isError:!1,completed:!1},t.tools.push(o),t.toolsById.set(s,o));const n=r.name??r.title;"tool"===o.name&&n&&(o.name=n),o.result=r.result??r.content??r.rawOutput??r.output??r.text,o.isError="failed"===r.status,o.completed=!0,this.upsertToolUse(e,t,o),this.upsertToolProgress(e,t,o),this.projectSearchSources(e,t,o.id,o.name,r.rawOutput,r.result,o.isError);for(const s of r.content??[]){const r=asRecord(asRecord(s)?.content),o=r?artifactFromBlock(r):void 0;o&&this.projectArtifact(e,t,o)}}upsertToolUse(e,t,r){const s=this.stream(e,t),o=toMessagePart({type:"tool_use",id:r.id,name:r.name,arguments:r.arguments,state:r.completed?"done":"streaming"});void 0===r.usePartIndex?(r.usePartIndex=s.parts.length,s.addPart(o)):s.replacePart(r.usePartIndex,o)}upsertToolProgress(e,t,r){if(void 0===r.result)return;const s=this.stream(e,t),o=toMessagePart({type:"custom",kind:"tool_progress",data:{tool_use_id:r.id,content:r.result,...r.isError?{is_error:!0}:{}}});void 0===r.progressPartIndex?(r.progressPartIndex=s.parts.length,s.addPart(o)):s.replacePart(r.progressPartIndex,o)}projectArtifact(e,t,r){const s=dedupeMessageServiceArtifacts([r])[0];if(!s||t.artifactUris.has(s.uri))return;t.artifactUris.add(s.uri),t.artifacts.push(s);const[o]=messageServiceFileParts([s]);o&&this.stream(e,t).addPart(o)}projectSearchSources(e,t,r,s,o,n,a){let i=o,c=0;try{let u=extractSearchSources({toolName:s,rawResult:i,isError:a});0===u.length&&void 0!==n&&n!==o&&(i=n,u=extractSearchSources({toolName:s,rawResult:i,isError:a}));const l=searchSourceProvider(i)??"unknown";if(c=u.length,0===c)return;const p=this.stream(e,t);for(const e of u){const s=t.sourcesByUrl.get(e.url);if(s){const r=mergeSearchSource(s.source,e);if(r.title===s.source.title&&r.snippet===s.source.snippet)continue;p.replacePart(s.partIndex,toMessagePart({type:"source",url:r.url,...r.title?{title:r.title}:{},...r.snippet?{snippet:r.snippet}:{},tool_use_id:s.toolUseId})),t.sourcesByUrl.set(e.url,{source:r,partIndex:s.partIndex,toolUseId:s.toolUseId});continue}const o=p.parts.length;p.addPart(toMessagePart({type:"source",url:e.url,...e.title?{title:e.title}:{},...e.snippet?{snippet:e.snippet}:{},tool_use_id:r})),t.sourcesByUrl.set(e.url,{source:e,partIndex:o,toolUseId:r})}this.deps.logger.debug?.(`[message-service-run-output] search source projection run=${e.runId} tool=${s} provider=${l} source_count=${c}`)}catch{const t=searchSourceProvider(i)??"unknown";this.deps.logger.warn?.(`[message-service-run-output] search source projection ignored run=${e.runId} tool=${s} provider=${t} source_count=${c}`)}}finishReasoning(e){e.stream&&void 0!==e.reasoningPartIndex&&e.stream.replacePart(e.reasoningPartIndex,toMessagePart({type:"thinking",text:e.reasoningText,state:"done"}))}finishTools(e){if(e.stream)for(const t of e.tools)void 0!==t.usePartIndex&&(t.completed||e.stream.replacePart(t.usePartIndex,toMessagePart({type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done"})))}stream(e,t){if(t.stream)return t.stream;const r=this.deps.getClient();if(!r)throw new Error(`Message Service client unavailable: ${e.runId}`);const s=messageServiceRunMessageId(e.runId);return t.stream=r.messages.startStream({conversationId:e.conversationId,clientMessageId:s,idempotencyKey:s,type:"agent_reply",content:{run_id:e.runId},...e.replyTo?{replyTo:e.replyTo}:{}}),t.stream}reportProjectionFailure(e,t,r){const s=t instanceof Error?t.message:String(t);this.deps.logger.warn?.(`[message-service-run-output] ${r} failed run=${e.runId}: ${s}`),this.deps.writeObs({component:"message-service-run-output",domain:"lifecycle",name:"run_output.failed",severity:"warn",summary:s,payload:{conversationId:e.conversationId,runId:e.runId,phase:r}})}state(e){const t=this.runs.get(e);if(!t)throw new Error(`Message Service run output is not open: ${e}`);return t}}function asRecord(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:void 0}function parseArguments(e){if(!e)return{};try{return JSON.parse(e)}catch{return e}}function artifactFromBlock(e){if("resource_link"!==e.type&&"file"!==e.type)return;const t="string"==typeof e.uri?e.uri:"string"==typeof e.url?e.url:void 0;return t?{uri:t,..."string"==typeof e.name?{name:e.name}:{},..."string"==typeof e.mimeType?{mimeType:e.mimeType}:"string"==typeof e.mime_type?{mimeType:e.mime_type}:{}}:void 0}
1
+ import{dedupeMessageServiceArtifacts,extractMessageToolCaption,messageServiceFileParts,messageServiceRunMessageId,sanitizeMessageServiceText}from"./message-service-output-format.js";import{toMessagePart}from"../message-client-types.js";import{extractSearchSources,mergeSearchSource,searchSourceProvider}from"./search-source-extractor.js";import{toMessageServiceCompletedStopReason}from"./message-service-stop-reason.js";import{messageStopReasonForRuntimeFailure}from"./im-runtime-failure-policy.js";import{writeRuntimeFailure}from"./write-runtime-failure.js";export class MessageServiceRunOutputChannel{deps;id="message_service";runs=new Map;constructor(e){this.deps=e}open(e){if(!e.conversationId)throw new Error(`Message Service run has no conversation: ${e.runId}`);this.runs.set(e.runId,{assistantText:"",reasoningText:"",artifacts:[],artifactUris:new Set,tools:[],toolsById:new Map,sourcesByUrl:new Map})}project(e,t){const r=this.state(e.runId);try{switch(t.type){case"assistant_delta":this.projectAssistantDelta(e,r,t.content);break;case"thought_chunk":this.projectThoughtDelta(e,r,t.content);break;case"tool_call":this.projectToolStart(e,r,t);break;case"tool_call_update":this.projectToolUpdate(e,r,t)}}catch(t){throw this.reportProjectionFailure(e,t,"project"),t}}async terminal(e,t,r){const s=this.state(e.runId);try{const o=dedupeMessageServiceArtifacts(s.artifacts);let n,a=sanitizeMessageServiceText(s.assistantText)||sanitizeMessageServiceText(s.caption);r.expectedUploadArtifacts&&0===o.length?(a="The generated attachment could not be delivered.",n="attachment_delivery_failed"):"failed"===t.kind&&(a||=t.message,n="interrupted"===t.mode?t.errorCode:"error");const i="failed"===r.persistenceReconciliation||"timed_out"===r.persistenceReconciliation;!i||a||0!==o.length||0!==s.tools.length||s.reasoningText||"complete"!==t.kind||(a="The response could not be delivered.",n=`persistence_reconciliation_${r.persistenceReconciliation}`),i&&this.deps.writeObs({component:"message-service-run-output",domain:"lifecycle",name:"timed_out"===r.persistenceReconciliation?"run_output.reconciliation_timeout":"run_output.reconciliation_failed",severity:"warn",payload:{conversationId:e.conversationId,runId:e.runId,status:r.persistenceReconciliation}});if(!(Boolean(a)||o.length>0||s.tools.length>0||Boolean(s.reasoningText))&&"complete"===t.kind)return;const c=this.stream(e,s);if(a!==s.assistantText&&c.setBody(a),this.finishReasoning(s),this.finishTools(s),"cancelled"===t.kind)await c.cancel({stopReason:"user_stop"});else if("attachment_delivery_failed"===n||n?.startsWith("persistence_reconciliation_"))await c.fail({stopReason:n});else if("failed"===t.kind)await writeRuntimeFailure(c,{body:a??"",stopReason:t.classification?messageStopReasonForRuntimeFailure(t.classification):"error",businessErrorCode:t.classification?.businessErrorCode});else{const e="complete"===t.kind?toMessageServiceCompletedStopReason(t.stopReason):"end_turn";await c.finalize({stopReason:e})}this.deps.writeObs({component:"message-service-run-output",domain:"lifecycle",name:n?"run_output.failed":"run_output.completed",severity:n?"warn":"info",payload:{conversationId:e.conversationId,runId:e.runId,textLen:a.length,artifactCount:o.length,toolCount:s.tools.length,sourceCount:s.sourcesByUrl.size}})}catch(t){throw this.reportProjectionFailure(e,t,"terminal"),t}finally{this.runs.delete(e.runId)}}projectAssistantDelta(e,t,r){const s=asRecord(r);if(!s)return;if("text"===s.type&&"string"==typeof s.text)return t.assistantText+=s.text,void this.stream(e,t).appendBody(s.text);const o=artifactFromBlock(s);o&&this.projectArtifact(e,t,o)}projectThoughtDelta(e,t,r){const s=asRecord(r);if("text"!==s?.type||"string"!=typeof s.text)return;t.reasoningText+=s.text;const o=this.stream(e,t),n=toMessagePart({type:"thinking",text:t.reasoningText,state:"streaming"});void 0===t.reasoningPartIndex?(t.reasoningPartIndex=o.parts.length,o.addPart(n)):o.replacePart(t.reasoningPartIndex,n)}projectToolStart(e,t,r){const s={id:String(r.toolCallId),name:r.name??r.title,arguments:r.rawInput??parseArguments(r.arguments),isError:!1,completed:!1};t.tools.push(s),t.toolsById.set(s.id,s),this.upsertToolUse(e,t,s);const o=extractMessageToolCaption(s.name,s.arguments);o&&(t.caption=o)}projectToolUpdate(e,t,r){const s=String(r.toolCallId);let o=t.toolsById.get(s);if(o||(o={id:s,name:r.name??r.title??"tool",arguments:{},isError:!1,completed:!1},t.tools.push(o),t.toolsById.set(s,o)),o.completed&&"completed"!==r.status&&"failed"!==r.status)return;const n=r.name??r.title;"tool"===o.name&&n&&(o.name=n),o.result=r.result??r.content??r.rawOutput??r.output??r.text,o.isError="failed"===r.status,o.completed="completed"===r.status||"failed"===r.status,this.upsertToolUse(e,t,o),this.upsertToolProgress(e,t,o),this.projectSearchSources(e,t,o.id,o.name,r.rawOutput,r.result,o.isError);for(const s of r.content??[]){const r=asRecord(asRecord(s)?.content),o=r?artifactFromBlock(r):void 0;o&&this.projectArtifact(e,t,o)}}upsertToolUse(e,t,r){const s=this.stream(e,t),o=toMessagePart({type:"tool_use",id:r.id,name:r.name,arguments:r.arguments,state:r.completed?"done":"streaming"});void 0===r.usePartIndex?(r.usePartIndex=s.parts.length,s.addPart(o)):s.replacePart(r.usePartIndex,o)}upsertToolProgress(e,t,r){if(void 0===r.result)return;const s=this.stream(e,t),o=toMessagePart({type:"custom",kind:"tool_progress",data:{tool_use_id:r.id,content:r.result,...r.isError?{is_error:!0}:{}}});void 0===r.progressPartIndex?(r.progressPartIndex=s.parts.length,s.addPart(o)):s.replacePart(r.progressPartIndex,o)}projectArtifact(e,t,r){const s=dedupeMessageServiceArtifacts([r])[0];if(!s||t.artifactUris.has(s.uri))return;t.artifactUris.add(s.uri),t.artifacts.push(s);const[o]=messageServiceFileParts([s]);o&&this.stream(e,t).addPart(o)}projectSearchSources(e,t,r,s,o,n,a){let i=o,c=0;try{let u=extractSearchSources({toolName:s,rawResult:i,isError:a});0===u.length&&void 0!==n&&n!==o&&(i=n,u=extractSearchSources({toolName:s,rawResult:i,isError:a}));const l=searchSourceProvider(i)??"unknown";if(c=u.length,0===c)return;const p=this.stream(e,t);for(const e of u){const s=t.sourcesByUrl.get(e.url);if(s){const r=mergeSearchSource(s.source,e);if(r.title===s.source.title&&r.snippet===s.source.snippet)continue;p.replacePart(s.partIndex,toMessagePart({type:"source",url:r.url,...r.title?{title:r.title}:{},...r.snippet?{snippet:r.snippet}:{},tool_use_id:s.toolUseId})),t.sourcesByUrl.set(e.url,{source:r,partIndex:s.partIndex,toolUseId:s.toolUseId});continue}const o=p.parts.length;p.addPart(toMessagePart({type:"source",url:e.url,...e.title?{title:e.title}:{},...e.snippet?{snippet:e.snippet}:{},tool_use_id:r})),t.sourcesByUrl.set(e.url,{source:e,partIndex:o,toolUseId:r})}this.deps.logger.debug?.(`[message-service-run-output] search source projection run=${e.runId} tool=${s} provider=${l} source_count=${c}`)}catch{const t=searchSourceProvider(i)??"unknown";this.deps.logger.warn?.(`[message-service-run-output] search source projection ignored run=${e.runId} tool=${s} provider=${t} source_count=${c}`)}}finishReasoning(e){e.stream&&void 0!==e.reasoningPartIndex&&e.stream.replacePart(e.reasoningPartIndex,toMessagePart({type:"thinking",text:e.reasoningText,state:"done"}))}finishTools(e){if(e.stream)for(const t of e.tools)void 0!==t.usePartIndex&&(t.completed||e.stream.replacePart(t.usePartIndex,toMessagePart({type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done"})))}stream(e,t){if(t.stream)return t.stream;const r=this.deps.getClient();if(!r)throw new Error(`Message Service client unavailable: ${e.runId}`);const s=messageServiceRunMessageId(e.runId);return t.stream=r.messages.startStream({conversationId:e.conversationId,clientMessageId:s,idempotencyKey:s,type:"agent_reply",content:{run_id:e.runId},...e.replyTo?{replyTo:e.replyTo}:{}}),t.stream}reportProjectionFailure(e,t,r){const s=t instanceof Error?t.message:String(t);this.deps.logger.warn?.(`[message-service-run-output] ${r} failed run=${e.runId}: ${s}`),this.deps.writeObs({component:"message-service-run-output",domain:"lifecycle",name:"run_output.failed",severity:"warn",summary:s,payload:{conversationId:e.conversationId,runId:e.runId,phase:r}})}state(e){const t=this.runs.get(e);if(!t)throw new Error(`Message Service run output is not open: ${e}`);return t}}function asRecord(e){return e&&"object"==typeof e&&!Array.isArray(e)?e:void 0}function parseArguments(e){if(!e)return{};try{return JSON.parse(e)}catch{return e}}function artifactFromBlock(e){if("resource_link"!==e.type&&"file"!==e.type)return;const t="string"==typeof e.uri?e.uri:"string"==typeof e.url?e.url:void 0;return t?{uri:t,..."string"==typeof e.name?{name:e.name}:{},..."string"==typeof e.mimeType?{mimeType:e.mimeType}:"string"==typeof e.mime_type?{mimeType:e.mime_type}:{}}:void 0}
@@ -0,0 +1 @@
1
+ const record=e=>{if("string"==typeof e&&e.length<=65536)try{e=JSON.parse(e)}catch{return{}}return null===e||"object"!=typeof e||Array.isArray(e)?{}:e},text=e=>"string"==typeof e?e:"",short=e=>{const t=e.replace(/[\u0000-\u001f\u007f]/g," ").trim(),n=Array.from(t);return n.length<=180?t:`${n.slice(0,180).join("")}…`},action=(e,t="")=>{const n=short(t);return n?{kind:e,subject:n}:{kind:e}},filename=e=>text(e).replaceAll("\\","/").split("/").at(-1)??"",toolName=e=>text(e).split(":").at(-1)??"";export function toolActivity(e,t){let n,s=toolName(e),r=record(t);for(let e=0;e<3&&"tool_call"===s;e++)s=toolName(r.id??r.name),r=record(r.args??r.arguments??r.input);switch(s){case"exec":case"exec_command":n=commandActions(text(r.command??r.cmd));break;case"beeos_search":case"web_search":case"search_web":n=[action("search",text(r.query))];break;case"beeos_fetch":case"web_fetch":{let e="";try{const t=new URL(text(r.url));"https:"!==t.protocol&&"http:"!==t.protocol||(e=t.hostname)}catch{}n=[action("page",e)];break}case"read":case"read_file":n=[action("read",filename(r.fileName??r.file_path??r.path))];break;case"write":case"edit":case"write_file":case"edit_file":case"beeos_upload_file":n=Array.isArray(r.paths)&&r.paths.length>0?r.paths.slice(0,5).map(e=>action("save",filename(e))):[action("save",filename(r.fileName??r.file_path??r.path))];break;case"tool_search":{const e=Array.isArray(r.queries)?r.queries:[],t=e.length>0&&e.every(e=>{const t=text(record(e).query).toLowerCase();return/\b(image|poster|graphic)\b/.test(t)&&/\b(generate|create|design)\b/.test(t)&&!/\b(not|never|without)\b/.test(t)});n=[action(t?"find_design_tools":"find_tools")];break}case"image_generate":{const e=text(r.prompt).trim(),t=/^(?:制作|生成|设计)[^不无非。!?\n]{0,60}海报[。!!]?$/i.test(e)||/^(?:create|generate|design) (?:a |an )?poster[.!]?$/i.test(e);n=[action(t?"generate_poster":"generate_image")];break}default:n=[action("other")]}return{schema_version:1,source:"tool_arguments",actions:n}}function commandActions(e){const t=[action("command")];if(!e||e.length>8192||/[$`\\(){}<>]/.test(e))return t;const n=/"[^"\n]*"|'[^'\n]*'|&&|\|\||[;|\n]|[^\s"';|&]+/g,s=[],r=[];let a=[],i=0,c="";for(const o of e.matchAll(n)){const n=e.slice(i,o.index);if(n.trim())return t;const l=o[0],h=[";","|","&&","||","\n"].includes(l);if(!h&&a.length>0&&0===n.length)return t;if(i=o.index+l.length,h){if(!a.length)return t;s.push(a),r.push(c),a=[],c=l}else a.push(l.startsWith('"')||l.startsWith("'")?l.slice(1,-1):l)}if(e.slice(i).trim()||!a.length&&["&&","||","|"].includes(c))return t;a.length&&(s.push(a),r.push(c));const o=[],l=new Set(["rsvg-convert","inkscape","chromium","chromium-browser","google-chrome","convert","magick","python3"]);for(const[e,n]of s.entries()){const[s,...a]=n,i=s.replace(/^\/(?:usr\/(?:local\/)?)?bin\/([A-Za-z0-9_.-]+)$/,"$1");let c;if("echo"!==i||1!==a.length||!["---","DONE"].includes(a[0])){if("which"===i&&a.length>0&&a.every(e=>l.has(e))||"command"===i&&2===a.length&&"-v"===a[0]&&l.has(a[1]))c=action("check_rendering");else if("fc-list"===i&&(0===a.length||1===a.length&&":lang=zh"===a[0]))c=action(0===a.length?"list_fonts":"list_chinese_fonts");else if("ls"!==i||0!==a.length&&(1!==a.length||a[0].startsWith("-"))){if("head"===i&&1===a.length&&/^-\d+$/.test(a[0])&&o.length>0&&"|"===r[e])continue;{const e="python3"===i&&"-m"===a[0]&&"pip"===a[1]?["pip",...a.slice(2)]:"uv"===i&&"pip"===a[0]?a:[i,...a];if("pip"!==e[0]&&"pip3"!==e[0]||"install"!==e[1]){if(!["python","python3","node"].includes(i)||1!==a.length||!/^(?:[A-Za-z0-9_./-]+\/)?[A-Za-z0-9_-][A-Za-z0-9_.-]*\.(?:py|js|mjs)$/.test(a[0]))return t;c=action("run_script",filename(a[0]))}else{const n=e.slice(2).filter(e=>!["--quiet","-q","--upgrade","-U","--user","--no-cache-dir"].includes(e));if(!n.length||n.length>6||n.some(e=>!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}(?:==[A-Za-z0-9_.-]{1,40})?$/.test(e)))return t;c=action("install",n.join(", "))}}}else c=action("list_files");o.some(e=>e.kind===c.kind&&e.subject===c.subject)||o.push(c)}}return o.length?o:t}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.6.74",
3
+ "version": "0.6.75",
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>",
@@ -90,5 +90,5 @@
90
90
  "brace-expansion": ">=5.0.9",
91
91
  "@beeos-ai/beeos-types": "$@beeos-ai/beeos-types"
92
92
  },
93
- "gitHead": "f19f918f953efae64f3927f8d9ba1a30dee4aa77"
93
+ "gitHead": "8a30ad4aa510b0ed608942881b4a9c3cd5ed2285"
94
94
  }