beeos-claw 0.6.74 → 0.6.76

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,97 @@ 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
+ Chinese font queries accept `fc-list :lang=zh` with optional `family`, `style`,
231
+ or `fullname` fields, followed by pure `sort -u` and `head -N` pipeline filters.
232
+ Unknown fields, extra file arguments, or other sort options fall back to `command`.
233
+ Directory listing accepts `ls` with `l`/`a`/`h` flags and at most one path.
234
+ `convert -version` and the complete fixed check
235
+ `python3 -c "import PIL; print(PIL.__version__)"` show `check_rendering`;
236
+ other inline Python remains `command`.
237
+ Headless `google-chrome` screenshots of local HTML to PNG use `generate_image`
238
+ with the output basename. Only known rendering flags are accepted; filenames
239
+ never imply `generate_poster`.
240
+ `identify` with one ordinary local PNG/JPEG/WebP/GIF path shows `read` and its basename.
241
+ `write`/`edit` calls show `save` with the file basename. Unknown tools use `other`.
242
+
243
+ `src/tool-activity.ts` reads actual tool names and arguments, including bounded
244
+ `tool_call` wrappers. It ignores `title`, `tasktitle`, and incoming `activity`.
245
+ Subjects are limited to search queries, URL hostnames, file basenames, and
246
+ allowlisted package names. Raw commands, inline code, URL credentials/query
247
+ strings, and full local paths are not activity subjects. The classifier never
248
+ executes commands and does not alter approval requirements.
249
+
250
+ Tool results and lifecycle events correlate by the explicit canonical call ID.
251
+ Early results wait for that exact card; anonymous summaries are ignored even
252
+ when just one tool is pending. A delayed duplicate cannot finish a different
253
+ call with the same name. Native retained replies persist host-observed results
254
+ by their call ID. Background `pending`/`in_progress` updates keep the tool
255
+ streaming; only `completed`/`failed` updates settle it. Late progress cannot
256
+ replace a terminal result. Error information remains in the matching
257
+ `tool_progress.data.is_error`, separate from activity.
258
+ Native hook results also read structured `isError` and `details` failure flags,
259
+ status, and timeout/error metadata. For `exec`/`exec_command`, a finite nonzero
260
+ numeric `details.exitCode` is a failure even when native status is `completed`.
261
+ Output text is never parsed for errors. `tool_use.state=done` means settled;
262
+ success or failure comes from the matching progress result.
263
+
264
+ Acceptance coverage lives in `message-client-types.test.ts`,
265
+ `runtime/im-reply-sink.test.ts`, `runtime/tool-result-observation-router.test.ts`, and
266
+ `runtime/message-service-run-output-channel.test.ts`: compound command actions,
267
+ misleading titles and prompts, secret-bearing shell/URL arguments, wrapped
268
+ calls, delayed anonymous approval errors, early results for a different ID,
269
+ approval waiting versus terminal updates, and structured native failures without
270
+ inferring errors from output text.
271
+
272
+ MS transport lifecycle, PATCH retries, fallback writes, and stop reasons remain
273
+ the SDK's responsibility. See the
274
+ [`@beeos-ai/message-sdk` README](../../sdks/message-sdk-node/README.md#streaming-agent-replies--envelope-v3).
211
275
 
212
276
  ### File Transfer
213
277
 
214
278
  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
279
 
280
+ Artifact generation guidance uses one executable per `exec` call so native
281
+ approval can bind the command and its file operands. Discovery uses `ls` / `read`
282
+ or a standalone `which`; shell builtins such as `command -v`, mixed commands,
283
+ redirections and pipelines are not reliable approval requests. A generated script
284
+ is written to a workspace file before a separate interpreter invocation. The
285
+ agent must wait for the real result, verify the artifact, and upload it before
286
+ claiming delivery. Binding errors and pending approvals are never completion,
287
+ and the guidance does not relax the host's approval policy.
288
+
216
289
  The plugin records office/PDF paths produced by `write` / `exec` and uploads any
217
290
  that were not already delivered. Tool-result enrichment attaches those files
218
291
  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}
@@ -1 +1 @@
1
- export class ToolResultObservationRouter{targets=new Map;register(t,e){const r=this.targets.get(t);if(r&&r!==e)throw new Error(`tool result observation target already registered for ${t}`);return this.targets.set(t,e),()=>{this.targets.get(t)===e&&this.targets.delete(t)}}route(t,e){const r=this.targets.get(t);return!!r&&(r.observeToolResult(e),!0)}size(){return this.targets.size}}
1
+ function structuredResultFailed(e){const t=e.rawResult;if(!t||"object"!=typeof t||Array.isArray(t))return!1;const r=t;if(!0===r.isError)return!0;if(!r.details||"object"!=typeof r.details||Array.isArray(r.details))return!1;const s=r.details;if(!1===s.ok||!1===s.success||!0===s.timedOut||Boolean(s.error))return!0;const i="string"==typeof s.status?s.status.trim().toLowerCase():"";return!!["error","failed","failure","timeout","timed_out","blocked","denied","forbidden","unavailable","approval-unavailable","disabled","aborted","cancelled","canceled","killed","invalid"].includes(i)||["exec","exec_command"].includes(e.toolName)&&"number"==typeof s.exitCode&&Number.isFinite(s.exitCode)&&0!==s.exitCode}export class ToolResultObservationRouter{targets=new Map;register(e,t){const r=this.targets.get(e);if(r&&r!==t)throw new Error(`tool result observation target already registered for ${e}`);return this.targets.set(e,t),()=>{this.targets.get(e)===t&&this.targets.delete(e)}}route(e,t){const r=this.targets.get(e);return!!r&&(r.observeToolResult({...t,isError:t.isError||structuredResultFailed(t)}),!0)}size(){return this.targets.size}}
@@ -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),i=record(t);for(let e=0;e<3&&"tool_call"===s;e++)s=toolName(i.id??i.name),i=record(i.args??i.arguments??i.input);switch(s){case"exec":case"exec_command":n=commandActions(text(i.command??i.cmd));break;case"beeos_search":case"web_search":case"search_web":n=[action("search",text(i.query))];break;case"beeos_fetch":case"web_fetch":{let e="";try{const t=new URL(text(i.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(i.fileName??i.file_path??i.path))];break;case"write":case"edit":case"write_file":case"edit_file":case"beeos_upload_file":n=Array.isArray(i.paths)&&i.paths.length>0?i.paths.slice(0,5).map(e=>action("save",filename(e))):[action("save",filename(i.fileName??i.file_path??i.path))];break;case"tool_search":{const e=Array.isArray(i.queries)?i.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(i.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)return t;if(/^(?:\/(?:usr\/(?:local\/)?)?bin\/)?python3[ \t]+-c[ \t]+(["'])import PIL; print\(PIL\.__version__\)\1$/.test(e.trim()))return[action("check_rendering")];if(/[$`\\(){}<>]/.test(e))return t;const n=/"[^"\n]*"|'[^'\n]*'|&&|\|\||[;|\n]|[^\s"';|&]+/g,s=[],i=[];let r=[],a=0,c="";for(const o of e.matchAll(n)){const n=e.slice(a,o.index);if(n.trim())return t;const l=o[0],h=[";","|","&&","||","\n"].includes(l);if(!h&&r.length>0&&0===n.length)return t;if(a=o.index+l.length,h){if(!r.length)return t;s.push(r),i.push(c),r=[],c=l}else r.push(l.startsWith('"')||l.startsWith("'")?l.slice(1,-1):l)}if(e.slice(a).trim()||!r.length&&["&&","||","|"].includes(c))return t;r.length&&(s.push(r),i.push(c));const o=[],l=new Set(["rsvg-convert","inkscape","chromium","chromium-browser","google-chrome","convert","magick","python3"]);let h=!1;for(const[e,n]of s.entries()){const s=h&&"|"===i[e];h=!1;const[r,...a]=n,c=r.replace(/^\/(?:usr\/(?:local\/)?)?bin\/([A-Za-z0-9_.-]+)$/,"$1");let u;if("echo"!==c||1!==a.length||!["---","DONE"].includes(a[0])){if("which"===c&&a.length>0&&a.every(e=>l.has(e))||"command"===c&&2===a.length&&"-v"===a[0]&&l.has(a[1])||"convert"===c&&1===a.length&&"-version"===a[0])u=action("check_rendering");else if("google-chrome"===c&&chromeScreenshot(a))u=action("generate_image",filename(a.find(e=>e.startsWith("--screenshot=")).slice(13)));else if("identify"===c&&1===a.length&&/^(?:[A-Za-z0-9_./-]+\/)?[A-Za-z0-9_-][A-Za-z0-9_.-]*\.(?:png|jpe?g|webp|gif)$/.test(a[0]))u=action("read",filename(a[0]));else if("fc-list"===c&&(0===a.length||":lang=zh"===a[0]&&a.slice(1).every(e=>["family","style","fullname"].includes(e))))u=action(0===a.length?"list_fonts":"list_chinese_fonts"),h=!0;else{if("sort"===c&&s&&1===a.length&&"-u"===a[0]){h=!0;continue}if("ls"===c&&a.filter(e=>!e.startsWith("-")).length<=1&&a.every(e=>!e.startsWith("-")||/^-[lah]+$/.test(e)))u=action("list_files");else{if("head"===c&&1===a.length&&/^-\d+$/.test(a[0])&&o.length>0&&"|"===i[e]){h=s;continue}{const e="python3"===c&&"-m"===a[0]&&"pip"===a[1]?["pip",...a.slice(2)]:"uv"===c&&"pip"===a[0]?a:[c,...a];if("pip"!==e[0]&&"pip3"!==e[0]||"install"!==e[1]){if(!["python","python3","node"].includes(c)||1!==a.length||!/^(?:[A-Za-z0-9_./-]+\/)?[A-Za-z0-9_-][A-Za-z0-9_.-]*\.(?:py|js|mjs)$/.test(a[0]))return t;u=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;u=action("install",n.join(", "))}}}}o.some(e=>e.kind===u.kind&&e.subject===u.subject)||o.push(u)}}return o.length?o:t}function chromeScreenshot(e){const t=e.map(e=>e.startsWith("--")?e.split("=",1)[0]:"input");return new Set(t).size===t.length&&e.includes("--headless=new")&&t.includes("--screenshot")&&t.includes("input")&&e.every(e=>["--headless=new","--no-sandbox","--disable-gpu","--hide-scrollbars"].includes(e)||/^--force-device-scale-factor=[1-4]$/.test(e)||/^--window-size=[1-9]\d{0,3},[1-9]\d{0,3}$/.test(e)||/^--screenshot=(?:[A-Za-z0-9_./-]+\/)?[A-Za-z0-9_-][A-Za-z0-9_.-]*\.png$/.test(e)||/^file:\/\/\/[A-Za-z0-9_./-]+\.html?$/.test(e))}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.6.74",
3
+ "version": "0.6.76",
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": "5f8dc0c2cdf809193708e9a22d8bffe705ec5c68"
94
94
  }