beeos-claw 0.6.66-cloud.10 → 0.6.66-cloud.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{toMessagePart}from"../message-client-types.js";import{approvalPresentation}from"./presentation.js";import{APPROVAL_PART_KIND,APPROVAL_SCHEMA_VERSION,readApprovalDocument}from"./protocol.generated.js";const NATIVE_DECISIONS=["allow-once","allow-always","deny"],ACTION_OF={"allow-once":"approve_once","allow-always":"approve_always",deny:"deny"},NATIVE_OF={approve_once:"allow-once",approve_always:"allow-always",deny:"deny"},record=e=>null!==e&&"object"==typeof e&&!Array.isArray(e),text=e=>"string"==typeof e&&e.length>0?e:void 0;export class OpenClawApprovalBridge{deps;sinks=new Map;pending=new Map;constructor(e){this.deps=e}bindSession(e,t){return this.sinks.set(e,t),()=>{if(this.sinks.get(e)===t){this.sinks.delete(e);for(const[e,r]of this.pending)r.sink===t&&this.pending.delete(e)}}}onGatewayEvent(e,t){switch(e){case"exec.approval.requested":return this.requested("exec",t);case"plugin.approval.requested":return this.requested("plugin",t);case"exec.approval.resolved":case"plugin.approval.resolved":return this.resolved(t);default:return}}async onDecision(e){const t=readApprovalDocument(e);if(!t||"approval_decision"!==t.kind)return!1;const r=this.pending.get(t.request_id);if(!r)return this.deps.logger.info?.(`[approval] decision for unknown or settled request_id=${t.request_id}`),!0;if(r.actionId)return!0;const i=NATIVE_OF[t.decision];if(!i||!r.request.available_actions.includes(t.decision))return this.deps.logger.warn?.(`[approval] unsupported decision=${t.decision} request_id=${t.request_id}`),!0;r.actionId=t.action_id;try{await this.deps.request("approval.resolve",{id:t.request_id,kind:r.kind,decision:i})}catch(e){r.actionId=void 0,this.deps.logger.warn?.(`[approval] approval.resolve failed request_id=${t.request_id}: ${e instanceof Error?e.message:String(e)}`)}return!0}requested(e,t){if(!record(t)||!record(t.request))return;const r=text(t.id),i=text(t.request.sessionKey);if(!r||!i||this.pending.has(r))return;const n=this.sinks.get(i);if(!n)return;const s=toApprovalRequest(e,r,t.request,t.expiresAtMs),o=n.add(approvalPart(s));void 0!==o&&this.pending.set(r,{kind:e,sink:n,index:o,request:s})}resolved(e){if(!record(e))return;const t=text(e.id),r=t?this.pending.get(t):void 0;t&&r&&(this.pending.delete(t),r.sink.replace(r.index,approvalPart(toApprovalResolved(t,e,r))))}}export function approvalPart(e){return toMessagePart({type:"custom",kind:APPROVAL_PART_KIND,data:e})}function toApprovalRequest(e,t,r,i){const n=Array.isArray(r.allowedDecisions)?NATIVE_DECISIONS.filter(e=>r.allowedDecisions.includes(e)):[...NATIVE_DECISIONS],s=Array.isArray(r.unavailableDecisions)?r.unavailableDecisions:[],o="exec"===e?{tool_name:"exec",summary:text(r.commandPreview)??text(r.command)??"",...text(r.cwd)?{detail:r.cwd}:{}}:{...text(r.toolName)?{tool_name:r.toolName}:{},summary:text(r.title)??"",...text(r.description)||text(r.detail)?{detail:[text(r.description),text(r.detail)].filter(Boolean).join("\n\n")}:{}},a="number"==typeof i&&Number.isFinite(i)?i:Date.now();return{schema_version:APPROVAL_SCHEMA_VERSION,kind:"approval_request",request_id:t,source:"exec"===e?"command":"plugin",operation:approvalPresentation(o,[]),available_actions:n.filter(e=>!s.includes(e)).map(e=>ACTION_OF[e]),expires_at:new Date(a).toISOString(),native:{kind:e,...approvalPresentation(r,[])}}}function toApprovalResolved(e,t,r){const{actionId:i}=r,n=text(t.terminalStatus),s=text(t.decision),o="number"==typeof t.ts&&Number.isFinite(t.ts)?t.ts:Date.now(),a="deny"===s&&!i&&o>=Date.parse(r.request.expires_at),d="expired"===n||!n&&a?"expired":"cancelled"===n?"cancelled":"deny"===s?"denied":s?"approved":n??"cancelled";return{schema_version:APPROVAL_SCHEMA_VERSION,kind:"approval_resolved",request_id:e,status:d,..."expired"===d?{reason:"timeout"}:"approved"===d||"denied"===d?{reason:"user"}:{},...!i||"approved"!==d&&"denied"!==d?{}:{action_id:i},resolved_at:new Date(o).toISOString()}}
1
+ import{toMessagePart}from"../message-client-types.js";import{approvalPresentation}from"./presentation.js";import{APPROVAL_PART_KIND,APPROVAL_SCHEMA_VERSION,readApprovalDocument}from"./protocol.generated.js";const NATIVE_DECISIONS=["allow-once","allow-always","deny"],ACTION_OF={"allow-once":"approve_once","allow-always":"approve_always",deny:"deny"},NATIVE_OF={approve_once:"allow-once",approve_always:"allow-always",deny:"deny"},record=e=>null!==e&&"object"==typeof e&&!Array.isArray(e),text=e=>"string"==typeof e&&e.length>0?e:void 0;export class OpenClawApprovalBridge{deps;sinks=new Map;pending=new Map;constructor(e){this.deps=e}bindSession(e,t){return this.sinks.set(e,t),()=>{if(this.sinks.get(e)===t){this.sinks.delete(e);for(const[e,r]of this.pending)r.sink===t&&this.pending.delete(e)}}}onGatewayEvent(e,t){switch(e){case"exec.approval.requested":return this.requested("exec",t);case"plugin.approval.requested":return this.requested("plugin",t);case"exec.approval.resolved":case"plugin.approval.resolved":return this.resolved(t);default:return}}async onDecision(e){const t=readApprovalDocument(e);if(!t||"approval_decision"!==t.kind)return!1;const r=this.pending.get(t.request_id);if(!r)return this.deps.logger.info?.(`[approval] decision for unknown or settled request_id=${t.request_id}`),!0;if(r.actionId)return!0;const i=NATIVE_OF[t.decision];if(!i||!r.request.available_actions.includes(t.decision))return this.deps.logger.warn?.(`[approval] unsupported decision=${t.decision} request_id=${t.request_id}`),!0;r.actionId=t.action_id;try{await this.deps.request("approval.resolve",{id:t.request_id,kind:r.kind,decision:i})}catch(e){r.actionId=void 0,this.deps.logger.warn?.(`[approval] approval.resolve failed request_id=${t.request_id}: ${e instanceof Error?e.message:String(e)}`)}return!0}requested(e,t){if(!record(t)||!record(t.request))return;const r=text(t.id),i=text(t.request.sessionKey);if(!r||!i||this.pending.has(r))return;const n=this.sinks.get(i);if(!n)return;const s=toApprovalRequest(e,r,t.request,t.expiresAtMs);void 0!==n.add(approvalPart(s))&&this.pending.set(r,{kind:e,sink:n,request:s})}resolved(e){if(!record(e))return;const t=text(e.id),r=t?this.pending.get(t):void 0;t&&r&&(this.pending.delete(t),r.sink.add(approvalPart(toApprovalResolved(t,e,r))))}}export function approvalPart(e){return toMessagePart({type:"custom",kind:APPROVAL_PART_KIND,data:e})}function toApprovalRequest(e,t,r,i){const n=Array.isArray(r.allowedDecisions)?NATIVE_DECISIONS.filter(e=>r.allowedDecisions.includes(e)):[...NATIVE_DECISIONS],s=Array.isArray(r.unavailableDecisions)?r.unavailableDecisions:[],o="exec"===e?{tool_name:"exec",summary:text(r.commandPreview)??text(r.command)??"",...text(r.cwd)?{detail:r.cwd}:{}}:{...text(r.toolName)?{tool_name:r.toolName}:{},summary:text(r.title)??"",...text(r.description)||text(r.detail)?{detail:[text(r.description),text(r.detail)].filter(Boolean).join("\n\n")}:{}},a="number"==typeof i&&Number.isFinite(i)?i:Date.now();return{schema_version:APPROVAL_SCHEMA_VERSION,kind:"approval_request",request_id:t,source:"exec"===e?"command":"plugin",operation:approvalPresentation(o,[]),available_actions:n.filter(e=>!s.includes(e)).map(e=>ACTION_OF[e]),expires_at:new Date(a).toISOString(),native:{kind:e,...approvalPresentation(r,[])}}}function toApprovalResolved(e,t,r){const{actionId:i}=r,n=text(t.terminalStatus),s=text(t.decision),o="number"==typeof t.ts&&Number.isFinite(t.ts)?t.ts:Date.now(),a="deny"===s&&!i&&o>=Date.parse(r.request.expires_at),d="expired"===n||!n&&a?"expired":"cancelled"===n?"cancelled":"deny"===s?"denied":s?"approved":n??"cancelled";return{schema_version:APPROVAL_SCHEMA_VERSION,kind:"approval_resolved",request_id:e,status:d,..."expired"===d?{reason:"timeout"}:"approved"===d||"denied"===d?{reason:"user"}:{},...!i||"approved"!==d&&"denied"!==d?{}:{action_id:i},resolved_at:new Date(o).toISOString()}}
@@ -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 m;const c={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 _=0,S=1;const x=(e,t={})=>({schema_version:1,seq:e,...t}),k=new Set,$=new Set,q=t=>{t!==o.text&&(t.startsWith(o.text)?e.stream.appendBody(t.slice(o.text.length)):e.stream.setBody(t),o.text=t)},w=e=>{void 0===e.publishTimer&&(e.publishTimer=setTimeout(()=>{e.publishTimer=void 0,de("body_preview_flush",()=>{m===e&&(e=>{o.text!==e.targetBody&&(q(e.targetBody),e.streamingPublishCount+=1)})(e)})},200))},T=e=>{const t=m;t?(void 0!==t.publishTimer&&(clearTimeout(t.publishTimer),t.publishTimer=void 0),t.targetBody=e,o.text!==e&&(q(e),t.terminalPublishCount+=1),c.receivedSnapshotCount+=t.receivedSnapshotCount,c.streamingPublishCount+=t.streamingPublishCount,c.terminalPublishCount+=t.terminalPublishCount,m=void 0):q(e)},C=t=>{if(void 0!==t.partIndex)return;const r=_,s=S;e.stream.addPart(toMessagePart({type:"text",text:t.text,state:"done",_seq:s,data:x(s)})),t.partIndex=r,_+=1,S+=1},E=()=>{if(!d){d=!0;for(const e of g)C(e)}},P=e=>{const t={text:e};g.push(t),d&&C(t)},R=(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:x(n.displaySeq)})),I.set(s.url,n);continue}const n={...s,partIndex:_,displaySeq:S,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:x(n.displaySeq)})),I.set(s.url,n),_+=1,S+=1,ae.source+=1}},M=(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:x(e.displaySeq,{started_at_ms:e.startedAtMs,...void 0!==s?{duration_ms:s}:{}})}),O=t=>{E();const r={partIndex:_,displaySeq:S,text:t,publishedText:t,receivedSnapshotCount:1,streamingPublishCount:1,startedAtMs:Date.now(),startedAtMonotonicMs:performance.now()};return e.stream.addPart(M(r,t,"streaming")),_+=1,S+=1,p=r,r},A=t=>{void 0===t.publishTimer&&(t.publishTimer=setTimeout(()=>{t.publishTimer=void 0,de("reasoning_snapshot_flush",()=>{p===t&&(t=>{t.publishedText!==t.text&&(e.stream.replacePart(t.partIndex,M(t,t.text,"streaming")),t.publishedText=t.text,t.streamingPublishCount+=1)})(t)})},200))},F=()=>{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,M(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})},N=e=>{const t=v.indexOf(e);t>=0&&v.splice(t,1)},U=e=>{const t=e?v.filter(t=>f.get(t)?.name===e):v;return 1===t.length?t[0]:void 0},j=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},L=(e,t)=>{if(h.set(e,t),e===t)return;const r=y.get(e);if(!r)return;const s=D(t);W(s,r),y.delete(e);const n=f.get(t);n&&(Y(n,s),y.delete(t))},B=e=>h.get(e)??e,z=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),D=e=>{let t=y.get(e);return t||(t={},y.set(e,t)),t},Q=(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},W=(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=Q(e.sources,t.sources)},J=(t,r,s)=>{const n="tool"===t.name&&"tool"!==r?r:t.name,o=!z(t.arguments)&&z(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:x(t.displaySeq)})),t.name=n,t.arguments=o)},H=(t,r,s,n)=>{const o=f.get(t);if(o)return J(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=z(s)?s:a?.arguments??s,d=((t,r,s)=>{const n=_,o=S;return e.stream.addPart(toMessagePart({type:"tool_use",id:t,name:r,arguments:s,state:"streaming",_seq:o,data:x(o)})),_+=1,S+=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&&Y(u,a),y.delete(t),u},K=(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||J(r,t.name??r.name,t.arguments??r.arguments),t.toolStartObserved&&(r.toolStartObserved=!0),t.itemEventObserved&&(r.itemEventObserved=!0),(t.pending||t.toolStartObserved||t.itemEventObserved)&&!v.includes(r.id)&&"streaming"===r.state&&v.push(r.id),r;const s=D(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)},G=t=>{"done"!==t.state&&(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done",_seq:t.displaySeq,data:x(t.displaySeq)})),t.state="done")},V=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(V).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${V(t)}`).join(",")}}`},X=(t,r,s)=>{const n=`${s?"1":"0"}:${V(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=_;return e.stream.addPart(o),t.progressPartIndex=r,t.progressSignature=n,void(_+=1)}e.stream.replacePart(t.progressPartIndex,o),t.progressSignature=n},Y=(e,t)=>{if(t.progress?.length)for(const r of t.progress)X(e,r.content,r.isError);t.observedResult&&(X(e,t.observedResult.content,t.observedResult.isError),e.observedResult=t.observedResult),(t.progress?.length||t.observedResult)&&(G(e),N(e.id)),t.sources?.length&&R(t.sources,e.id)},Z=e=>[...new Set([e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e)).map(e=>e.trim()).filter(Boolean))],ee=(e,t=[])=>{const r=[...new Set([...Z(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.":""},te=t=>{for(const r of t)e.stream.addPart(toMessagePart({type:"resource_link",uri:r.uri,name:r.name,...r.mimeType?{mimeType:r.mimeType}:{}})),_+=1},re=t=>{k.add(t);const r=()=>{k.delete(t)};e.onCallbackFailure?t.then(r,r):t.finally(r)},se=(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:Z(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;X(t,ee(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),_+=1,te(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});re(n)},ne=(e,t,r)=>{const s=h.get(e);if(s)return K(s,{name:t,arguments:r});const n=K(e,{name:t,arguments:r});if(n)return n;const o=U(t);if(o)return L(e,o),K(o,{name:t,arguments:r});K(e,{name:t,arguments:r})},oe=(e,t)=>{const r=h.get(e);if(r)return K(r,{name:t,itemEventObserved:!0,pending:!0});const s=K(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 L(e,n),K(n,{name:t,itemEventObserved:!0,pending:!0});const o=j(t);K(e,{name:t,arguments:o?.arguments,itemEventObserved:!0,pending:!0})},ae={tool:0,block:0,final:0,source:0};let ie,le=!1;const de=(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):!le||(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)}})};return{dispatcher:{sendToolResult:t=>(de("tool_progress",()=>{const r=resolveRuntimeToolCallId(t),s=resolveRuntimeToolName(t),n=resolveRuntimeToolArguments(t),o=ee(t),a=!0===t.isError;let i;if(r){if(i=ne(r,s,n),!i){const e=D(r);return s&&(e.name=s),z(n)&&(e.arguments=n),e.progress=[...e.progress??[],{content:o,isError:a}],void(ae.tool+=1)}}else{const t=s?U(s):U();if(!t)return j(s),void e.logger.debug?.(`[im-sink] unattributed tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${s??"unknown"} pending=${v.length}`);i=K(t,{name:s,arguments:n})}i&&(ae.tool+=1,i.observedResult||X(i,o,a),G(i),N(i.id),se(i,t))}),!0),sendBlockReply:e=>(de("block_reply",()=>{ae.block+=1,isNonEmptyString(e.text)&&(ie=!0!==e.isError?void 0:e.text)}),!0),sendFinalReply:t=>(de("final_reply",()=>{var r;(ae.final+=1,!0!==t.isError)?((t=>{const r=Z(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;te(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)})();re(s)})(t),isNonEmptyString(t.text)&&(ie=void 0,l||(r=t.text,i="",a+=r,T(a),P(r),l=!0))):ie=t.text}),!0),waitForIdle:async()=>{for(let t=0;(e.onCallbackFailure||t<16)&&(await e.callbackQueue.waitForIdle(),0!==k.size);t++)e.onCallbackFailure?await Promise.all([...k]):await Promise.allSettled([...k]);await e.callbackQueue.waitForIdle();const t=takeFinalizeAttachments(e.sessionKey);t.length>0&&!e.abortSignal.aborted&&!e.stream.isTerminated&&te(t)},getQueuedCounts:()=>({tool:ae.tool,block:ae.block,final:ae.final}),getAccumulatedBody:()=>o.text,getTerminalErrorText:()=>ie,markComplete:r=>{F(),(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),c.receivedSnapshotCount>0&&logChatLatency(e.logger,"claw.text_preview.settled",e.requestId,t,{text_preview_snapshot_count:c.receivedSnapshotCount,text_preview_streaming_publish_count:c.streamingPublishCount,text_preview_terminal_publish_count:c.terminalPublishCount,text_preview_coalesced_snapshot_count:Math.max(0,c.receivedSnapshotCount-c.streamingPublishCount-c.terminalPublishCount),text_preview_publish_interval_ms:200});for(const t of f.values())try{"success"===r&&isUploadArtifactToolName(t.name)&&!t.durableUploadFilesEmitted&&se(t,{toolCallId:t.id,toolName:t.name}),"success"===r&&G(t),N(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(),le=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,disableBlockStreaming:!0,onAgentRunStart:r=>{e.onAgentRunStart?.(r),logChatLatency(e.logger,"claw.runtime.run_started",e.requestId,t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${r} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:s=>{de("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=m;if(!t){if(o.text===e)return;return q(e),void(m={targetBody:e,receivedSnapshotCount:1,streamingPublishCount:1,terminalPublishCount:0})}t.targetBody!==e&&(t.targetBody=e,t.receivedSnapshotCount+=1,w(t))})(a+i))})},onReasoningStream:e=>{de("reasoning",()=>{if(!isNonEmptyString(e.text))return;const t=e.text,r=p??O(t);t!==r.text&&(r.text.startsWith(t)||(t.startsWith(r.text)?(r.text=t,r.receivedSnapshotCount+=1,A(r)):(F(),O(t))))})},onReasoningEnd:()=>{de("reasoning_end",()=>{F()})},onAssistantMessageStart:()=>{de("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=>{de("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&&K(B(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){K(a,{name:o,arguments:i,toolStartObserved:!0,pending:!0});const e=U(o);return void(e&&e!==a&&!h.has(a)&&(L(a,e),K(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 K(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}):K(u[0][0],{name:o,arguments:i,toolStartObserved:!0,pending:!0})})},onItemEvent:e=>{de("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?oe(s,n):K(B(s),{name:n,itemEventObserved:!0,pending:!0}))})}},commitAssistantMessage:e=>{de("assistant_message_commit",()=>{if(u.has(e.key))return;u.add(e.key),l=!0,ie=void 0,i="";const t=e.segments.filter(e=>"text"===e.type).map(e=>e.text).join("");a+=t,T(a);for(const t of e.segments)"text"!==t.type?H(t.id,t.name,t.arguments,!0):P(t.text)})},observeToolResult:r=>{de("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=ne(o,a);if(t)X(t,ee({text:e,isError:r.isError},t.artifactMediaUrls),r.isError),t.observedResult={content:e,isError:r.isError},G(t),N(t.id);else{const t=D(B(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=ne(o,a);if(s)R(t,s.id);else{const e=D(B(o));e.name??=a,e.sources=Q(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:()=>ae.source,approvalParts:{add(t){if(e.stream.isTerminated)return;const r=_;return e.stream.addPart(t),_+=1,r},replace(t,r){e.stream.isTerminated||e.stream.replacePart(t,r)}}}}
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 m;const c={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 _=0,S=1;const x=(e,t={})=>({schema_version:1,seq:e,...t}),k=new Set,$=new Set,q=t=>{t!==o.text&&(t.startsWith(o.text)?e.stream.appendBody(t.slice(o.text.length)):e.stream.setBody(t),o.text=t)},w=e=>{void 0===e.publishTimer&&(e.publishTimer=setTimeout(()=>{e.publishTimer=void 0,de("body_preview_flush",()=>{m===e&&(e=>{o.text!==e.targetBody&&(q(e.targetBody),e.streamingPublishCount+=1)})(e)})},200))},C=e=>{const t=m;t?(void 0!==t.publishTimer&&(clearTimeout(t.publishTimer),t.publishTimer=void 0),t.targetBody=e,o.text!==e&&(q(e),t.terminalPublishCount+=1),c.receivedSnapshotCount+=t.receivedSnapshotCount,c.streamingPublishCount+=t.streamingPublishCount,c.terminalPublishCount+=t.terminalPublishCount,m=void 0):q(e)},T=t=>{if(void 0!==t.partIndex)return;const r=_,s=S;e.stream.addPart(toMessagePart({type:"text",text:t.text,state:"done",_seq:s,data:x(s)})),t.partIndex=r,_+=1,S+=1},E=()=>{if(!d){d=!0;for(const e of g)T(e)}},P=e=>{const t={text:e};g.push(t),d&&T(t)},R=(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:x(n.displaySeq)})),I.set(s.url,n);continue}const n={...s,partIndex:_,displaySeq:S,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:x(n.displaySeq)})),I.set(s.url,n),_+=1,S+=1,ae.source+=1}},M=(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:x(e.displaySeq,{started_at_ms:e.startedAtMs,...void 0!==s?{duration_ms:s}:{}})}),O=t=>{E();const r={partIndex:_,displaySeq:S,text:t,publishedText:t,receivedSnapshotCount:1,streamingPublishCount:1,startedAtMs:Date.now(),startedAtMonotonicMs:performance.now()};return e.stream.addPart(M(r,t,"streaming")),_+=1,S+=1,p=r,r},A=t=>{void 0===t.publishTimer&&(t.publishTimer=setTimeout(()=>{t.publishTimer=void 0,de("reasoning_snapshot_flush",()=>{p===t&&(t=>{t.publishedText!==t.text&&(e.stream.replacePart(t.partIndex,M(t,t.text,"streaming")),t.publishedText=t.text,t.streamingPublishCount+=1)})(t)})},200))},F=()=>{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,M(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})},N=e=>{const t=v.indexOf(e);t>=0&&v.splice(t,1)},U=e=>{const t=e?v.filter(t=>f.get(t)?.name===e):v;return 1===t.length?t[0]:void 0},j=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},L=(e,t)=>{if(h.set(e,t),e===t)return;const r=y.get(e);if(!r)return;const s=D(t);W(s,r),y.delete(e);const n=f.get(t);n&&(Y(n,s),y.delete(t))},B=e=>h.get(e)??e,z=e=>null!=e&&("object"!=typeof e||(Array.isArray(e)?e.length>0:Object.keys(e).length>0)),D=e=>{let t=y.get(e);return t||(t={},y.set(e,t)),t},Q=(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},W=(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=Q(e.sources,t.sources)},J=(t,r,s)=>{const n="tool"===t.name&&"tool"!==r?r:t.name,o=!z(t.arguments)&&z(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:x(t.displaySeq)})),t.name=n,t.arguments=o)},H=(t,r,s,n)=>{const o=f.get(t);if(o)return J(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=z(s)?s:a?.arguments??s,d=((t,r,s)=>{const n=_,o=S;return e.stream.addPart(toMessagePart({type:"tool_use",id:t,name:r,arguments:s,state:"streaming",_seq:o,data:x(o)})),_+=1,S+=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&&Y(u,a),y.delete(t),u},K=(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||J(r,t.name??r.name,t.arguments??r.arguments),t.toolStartObserved&&(r.toolStartObserved=!0),t.itemEventObserved&&(r.itemEventObserved=!0),(t.pending||t.toolStartObserved||t.itemEventObserved)&&!v.includes(r.id)&&"streaming"===r.state&&v.push(r.id),r;const s=D(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)},G=t=>{"done"!==t.state&&(e.stream.replacePart(t.partIndex,toMessagePart({type:"tool_use",id:t.id,name:t.name,arguments:t.arguments,state:"done",_seq:t.displaySeq,data:x(t.displaySeq)})),t.state="done")},V=e=>{if(null===e||"object"!=typeof e)return JSON.stringify(e)??String(e);if(Array.isArray(e))return`[${e.map(V).join(",")}]`;return`{${Object.entries(e).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${V(t)}`).join(",")}}`},X=(t,r,s)=>{const n=`${s?"1":"0"}:${V(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=_;return e.stream.addPart(o),t.progressPartIndex=r,t.progressSignature=n,void(_+=1)}e.stream.replacePart(t.progressPartIndex,o),t.progressSignature=n},Y=(e,t)=>{if(t.progress?.length)for(const r of t.progress)X(e,r.content,r.isError);t.observedResult&&(X(e,t.observedResult.content,t.observedResult.isError),e.observedResult=t.observedResult),(t.progress?.length||t.observedResult)&&(G(e),N(e.id)),t.sources?.length&&R(t.sources,e.id)},Z=e=>[...new Set([e.mediaUrl,...e.mediaUrls??[]].filter(e=>isNonEmptyString(e)).map(e=>e.trim()).filter(Boolean))],ee=(e,t=[])=>{const r=[...new Set([...Z(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.":""},te=t=>{for(const r of t)e.stream.addPart(toMessagePart({type:"resource_link",uri:r.uri,name:r.name,...r.mimeType?{mimeType:r.mimeType}:{}})),_+=1},re=t=>{k.add(t);const r=()=>{k.delete(t)};e.onCallbackFailure?t.then(r,r):t.finally(r)},se=(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:Z(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;X(t,ee(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),_+=1,te(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});re(n)},ne=(e,t,r)=>{const s=h.get(e);if(s)return K(s,{name:t,arguments:r});const n=K(e,{name:t,arguments:r});if(n)return n;const o=U(t);if(o)return L(e,o),K(o,{name:t,arguments:r});K(e,{name:t,arguments:r})},oe=(e,t)=>{const r=h.get(e);if(r)return K(r,{name:t,itemEventObserved:!0,pending:!0});const s=K(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 L(e,n),K(n,{name:t,itemEventObserved:!0,pending:!0});const o=j(t);K(e,{name:t,arguments:o?.arguments,itemEventObserved:!0,pending:!0})},ae={tool:0,block:0,final:0,source:0};let ie,le=!1;const de=(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):!le||(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)}})};return{dispatcher:{sendToolResult:t=>(de("tool_progress",()=>{const r=resolveRuntimeToolCallId(t),s=resolveRuntimeToolName(t),n=resolveRuntimeToolArguments(t),o=ee(t),a=!0===t.isError;let i;if(r){if(i=ne(r,s,n),!i){const e=D(r);return s&&(e.name=s),z(n)&&(e.arguments=n),e.progress=[...e.progress??[],{content:o,isError:a}],void(ae.tool+=1)}}else{const t=s?U(s):U();if(!t)return j(s),void e.logger.debug?.(`[im-sink] unattributed tool progress ignored requestId=${e.requestId} channel=${e.channelId} tool=${s??"unknown"} pending=${v.length}`);i=K(t,{name:s,arguments:n})}i&&(ae.tool+=1,i.observedResult||X(i,o,a),G(i),N(i.id),se(i,t))}),!0),sendBlockReply:e=>(de("block_reply",()=>{ae.block+=1,isNonEmptyString(e.text)&&(ie=!0!==e.isError?void 0:e.text)}),!0),sendFinalReply:t=>(de("final_reply",()=>{var r;(ae.final+=1,!0!==t.isError)?((t=>{const r=Z(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;te(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)})();re(s)})(t),isNonEmptyString(t.text)&&(ie=void 0,l||(r=t.text,i="",a+=r,C(a),P(r),l=!0))):ie=t.text}),!0),waitForIdle:async()=>{for(let t=0;(e.onCallbackFailure||t<16)&&(await e.callbackQueue.waitForIdle(),0!==k.size);t++)e.onCallbackFailure?await Promise.all([...k]):await Promise.allSettled([...k]);await e.callbackQueue.waitForIdle();const t=takeFinalizeAttachments(e.sessionKey);t.length>0&&!e.abortSignal.aborted&&!e.stream.isTerminated&&te(t)},getQueuedCounts:()=>({tool:ae.tool,block:ae.block,final:ae.final}),getAccumulatedBody:()=>o.text,getTerminalErrorText:()=>ie,markComplete:r=>{F(),(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="",C(a),c.receivedSnapshotCount>0&&logChatLatency(e.logger,"claw.text_preview.settled",e.requestId,t,{text_preview_snapshot_count:c.receivedSnapshotCount,text_preview_streaming_publish_count:c.streamingPublishCount,text_preview_terminal_publish_count:c.terminalPublishCount,text_preview_coalesced_snapshot_count:Math.max(0,c.receivedSnapshotCount-c.streamingPublishCount-c.terminalPublishCount),text_preview_publish_interval_ms:200});for(const t of f.values())try{"success"===r&&isUploadArtifactToolName(t.name)&&!t.durableUploadFilesEmitted&&se(t,{toolCallId:t.id,toolName:t.name}),"success"===r&&G(t),N(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(),le=!0}},replyOptions:{...e.runId?{runId:e.runId}:{},abortSignal:e.abortSignal,disableBlockStreaming:!0,onAgentRunStart:r=>{e.onAgentRunStart?.(r),logChatLatency(e.logger,"claw.runtime.run_started",e.requestId,t),e.logger.info?.(`[im-sink] run_start requestId=${e.requestId} runId=${r} channel=${e.channelId} in_reply_to=${e.inboundMessageId} origin=${e.origin??"im"}`)},onPartialReply:s=>{de("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=m;if(!t){if(o.text===e)return;return q(e),void(m={targetBody:e,receivedSnapshotCount:1,streamingPublishCount:1,terminalPublishCount:0})}t.targetBody!==e&&(t.targetBody=e,t.receivedSnapshotCount+=1,w(t))})(a+i))})},onReasoningStream:e=>{de("reasoning",()=>{if(!isNonEmptyString(e.text))return;const t=e.text,r=p??O(t);t!==r.text&&(r.text.startsWith(t)||(t.startsWith(r.text)?(r.text=t,r.receivedSnapshotCount+=1,A(r)):(F(),O(t))))})},onReasoningEnd:()=>{de("reasoning_end",()=>{F()})},onAssistantMessageStart:()=>{de("assistant_message_start",()=>{i="",l=!1,C(a),e.logger.debug?.(`[im-sink] assistant_message_start requestId=${e.requestId} channel=${e.channelId} in_reply_to=${e.inboundMessageId}`)})},onToolStart:r=>{de("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&&K(B(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){K(a,{name:o,arguments:i,toolStartObserved:!0,pending:!0});const e=U(o);return void(e&&e!==a&&!h.has(a)&&(L(a,e),K(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 K(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}):K(u[0][0],{name:o,arguments:i,toolStartObserved:!0,pending:!0})})},onItemEvent:e=>{de("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?oe(s,n):K(B(s),{name:n,itemEventObserved:!0,pending:!0}))})}},commitAssistantMessage:e=>{de("assistant_message_commit",()=>{if(u.has(e.key))return;u.add(e.key),l=!0,ie=void 0,i="";const t=e.segments.filter(e=>"text"===e.type).map(e=>e.text).join("");a+=t,C(a);for(const t of e.segments)"text"!==t.type?H(t.id,t.name,t.arguments,!0):P(t.text)})},observeToolResult:r=>{de("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=ne(o,a);if(t)X(t,ee({text:e,isError:r.isError},t.artifactMediaUrls),r.isError),t.observedResult={content:e,isError:r.isError},G(t),N(t.id);else{const t=D(B(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=ne(o,a);if(s)R(t,s.id);else{const e=D(B(o));e.name??=a,e.sources=Q(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:()=>ae.source,approvalParts:{add(t){if(e.stream.isTerminated)return;const r=_;return e.stream.addPart(t),_+=1,r}}}}
@@ -1 +1 @@
1
- import{existsSync,readFileSync}from"node:fs";import{isAbsolute,join}from"node:path";import{resolveOpenClawStateDir}from"./openclaw-state-dir.js";const DEFAULT_POLL_INTERVAL_MS=250,DEFAULT_QUIET_CONFIRMATION_MS=750;export class OpenClawSessionWriteQuiescence{logger;stateDir;defaultAgentId;pollIntervalMs;quietConfirmationMs;now;sleep;constructor(e,s={}){this.logger=e,this.stateDir=s.stateDir??resolveOpenClawStateDir(),this.defaultAgentId=s.agentId??"main",this.pollIntervalMs=s.pollIntervalMs??250,this.quietConfirmationMs=s.quietConfirmationMs??750,this.now=s.now??(()=>Date.now()),this.sleep=s.sleep??(e=>new Promise(s=>{const t=setTimeout(s,e);t.unref?.()}))}async waitForQuiescence(e,s){const t=this.now();let i,o,n;for(;this.now()-t<s;){const s=n??this.resolveLockPath(e);if(s){if(o=s,n=s,existsSync(s))i=void 0;else if(i??=this.now(),this.now()-i>=this.quietConfirmationMs)return{quiescent:!0,reason:"unlocked",lockPath:s,elapsedMs:this.now()-t};await this.sleep(this.pollIntervalMs)}else await this.sleep(this.pollIntervalMs)}return this.logger.warn?.(`[im] session writer did not become quiescent sessionKey=${e} timeout_ms=${s}`),{quiescent:!1,reason:o?"timeout":"session_unresolved",...o?{lockPath:o}:{},elapsedMs:this.now()-t}}resolveLockPath(e){const s=/^agent:([^:]+):/.exec(e)?.[1]??this.defaultAgentId,t=join(this.stateDir,"agents",s,"sessions"),i=join(t,"sessions.json");if(!existsSync(i))return;let o;try{const s=JSON.parse(readFileSync(i,"utf8"));if(!s||"object"!=typeof s)return;const t=s[e];t&&"object"==typeof t&&(o=t)}catch(s){return void this.logger.debug?.(`[im] session index read deferred sessionKey=${e} error=${s instanceof Error?s.message:String(s)}`)}if(!o)return;const n="string"==typeof o.sessionFile?o.sessionFile.trim():"",r="string"==typeof o.sessionId?o.sessionId.trim():"",l=n?isAbsolute(n)?n:join(t,n):r?join(t,`${r}.jsonl`):"";return l?`${l}.lock`:void 0}}
1
+ import{existsSync,readFileSync}from"node:fs";import{isAbsolute,join}from"node:path";import{resolveOpenClawStateDir}from"./openclaw-state-dir.js";const DEFAULT_POLL_INTERVAL_MS=250,DEFAULT_QUIET_CONFIRMATION_MS=750,DEFAULT_UNRESOLVED_GRACE_MS=1e4;export class OpenClawSessionWriteQuiescence{logger;stateDir;defaultAgentId;pollIntervalMs;quietConfirmationMs;unresolvedGraceMs;now;sleep;constructor(e,s={}){this.logger=e,this.stateDir=s.stateDir??resolveOpenClawStateDir(),this.defaultAgentId=s.agentId??"main",this.pollIntervalMs=s.pollIntervalMs??250,this.quietConfirmationMs=s.quietConfirmationMs??750,this.unresolvedGraceMs=s.unresolvedGraceMs??1e4,this.now=s.now??(()=>Date.now()),this.sleep=s.sleep??(e=>new Promise(s=>{const t=setTimeout(s,e);t.unref?.()}))}async waitForQuiescence(e,s){const t=this.now();let i,o,n;for(;this.now()-t<s;){const s=n??this.resolveLockPath(e);if(s){if(o=s,n=s,existsSync(s))i=void 0;else if(i??=this.now(),this.now()-i>=this.quietConfirmationMs)return{quiescent:!0,reason:"unlocked",lockPath:s,elapsedMs:this.now()-t};await this.sleep(this.pollIntervalMs)}else{if(this.hasSqliteSessionStore(e))return{quiescent:!0,reason:"native_writer_fence",elapsedMs:this.now()-t};if(this.now()-t>=this.unresolvedGraceMs)break;await this.sleep(this.pollIntervalMs)}}return this.logger.warn?.(`[im] session writer did not become quiescent sessionKey=${e} timeout_ms=${s}`),{quiescent:!1,reason:o?"timeout":"session_unresolved",...o?{lockPath:o}:{},elapsedMs:this.now()-t}}agentRoot(e){const s=/^agent:([^:]+):/.exec(e)?.[1]??this.defaultAgentId;return join(this.stateDir,"agents",s)}hasSqliteSessionStore(e){return existsSync(join(this.agentRoot(e),"agent","openclaw-agent.sqlite"))}resolveLockPath(e){const s=join(this.agentRoot(e),"sessions"),t=join(s,"sessions.json");if(!existsSync(t))return;let i;try{const s=JSON.parse(readFileSync(t,"utf8"));if(!s||"object"!=typeof s)return;const o=s[e];o&&"object"==typeof o&&(i=o)}catch(s){return void this.logger.debug?.(`[im] session index read deferred sessionKey=${e} error=${s instanceof Error?s.message:String(s)}`)}if(!i)return;const o="string"==typeof i.sessionFile?i.sessionFile.trim():"",n="string"==typeof i.sessionId?i.sessionId.trim():"",r=o?isAbsolute(o)?o:join(s,o):n?join(s,`${n}.jsonl`):"";return r?`${r}.lock`:void 0}}
@@ -1 +1 @@
1
- import{randomUUID}from"node:crypto";import{performance}from"node:perf_hooks";import{RuntimeCommandError}from"./runtime-command-dispatcher.js";import{ClearConversationUseCase}from"./conversation-clear-use-case.js";import{createSessionModelRefs}from"./openclaw-session-model.js";import{openClawSessionKeysFromList}from"./openclaw-session-key-resolution.js";async function modelSwitchTimingSpan(e,t,i){const s=performance.now();let n="succeeded";try{return await i()}catch(e){throw n="failed",e}finally{e&&process.stderr.write(`[MODEL_SWITCH_TIMING] component=runtime operationId=${e} stage=${t} outcome=${n} durationMs=${(performance.now()-s).toFixed(1)}\n`)}}export class RuntimeSessionControlService{clearRuntime;fallback;activeRuns;constructor(e,t,i){this.clearRuntime=e,this.fallback=t,this.activeRuns=i}async execute(e,t,i){const s=e;if("session.clear"===s.name){if(!i.historyBoundary)throw new RuntimeCommandError("INVALID_PARAMS",`${i.origin} clear attempt lacks its history boundary`);const e=s.context.conversation;return await new ClearConversationUseCase(i.historyBoundary,this.clearRuntime).execute({operationId:s.context.operationId,conversationId:e.runtimeConversationUid,runtimeSessionKey:e.runtimeSessionKey,runtimeEpoch:s.context.dispatchEpoch})}if("session.cancel"===s.name){if(!this.activeRuns)throw new RuntimeCommandError("INVALID_PARAMS","session.cancel active-run authority is not configured");const e=s;return await this.activeRuns.cancelExact({runtimeConversationUid:e.context.conversation.runtimeConversationUid,runtimeSessionKey:e.context.conversation.runtimeSessionKey,historyGeneration:e.context.historyGeneration,targetMessageId:e.params.targetMessageId}),{}}if(!this.fallback)throw new Error(`session command delegate is not configured: ${s.name}`);return await this.fallback.execute(s,t,i)}async projectCommitted(e,t,i){return await(this.fallback?.projectCommitted?.(e,t,i))}}export class OpenClawSessionMetadataService{metadata;constructor(e){this.metadata="function"==typeof e?new OpenClawGatewaySessionMetadataPort(e):e}async execute(e,t,i){const s=e,n=s.context.conversation.runtimeSessionKey,o={lease:t,attempt:i};switch(s.name){case"session.load":return await this.metadata.load(n,o);case"session.rename":{const e=s.params.title.slice(0,64);return await this.metadata.rename(n,e,o)}case"session.delete":return await this.metadata.delete(n,o);case"session.setMode":{const e=s.params.mode;return await this.metadata.setMode(n,e,o)}case"session.setModel":{const e=s.params.modelOverrideId;if(null!==e&&("string"!=typeof e||0===e.length||e.trim()!==e))throw new RuntimeCommandError("INVALID_PARAMS","modelOverrideId must be a non-empty string or null");const t=null===e?await modelSwitchTimingSpan(i.operationId,"resolve_agent_default_model",async()=>await this.metadata.resolveAgentDefaultModel(n,o)):void 0;if(null===e&&!t)throw new RuntimeCommandError("AGENT_DEFAULT_MODEL_NOT_CONFIGURED","agent default model is not configured");return await modelSwitchTimingSpan(i.operationId,"metadata_set_model",async()=>await this.metadata.setModel(n,e,t??void 0,o))}}throw new RuntimeCommandError("INVALID_PARAMS","unsupported session metadata command")}async projectCommitted(e,t,i){if("session.setModel"!==e.name)return;const s=e,n=t;if(this.metadata.projectCommittedSetModel&&await this.metadata.projectCommittedSetModel(s.context.conversation.runtimeSessionKey,n),"service"===i?.origin){if(!i.sessionModelProjection)throw new Error("Message Service session model projection is unavailable");return Object.freeze({state:"pending",revision:"0"})}if(!this.metadata.projectCommittedSetModel)throw new Error("session metadata projection is unavailable")}}export class OpenClawGatewaySessionMetadataPort{getGateway;projectModel;liveSessionKeys;modelMutations;constructor(e,t,i,s){this.getGateway=e,this.projectModel=t,this.liveSessionKeys=i,this.modelMutations=s}modelRefs(){return createSessionModelRefs()}async load(e,t){const i=await this.requireExistingOpenClawKey(e,t);return await this.gateway().request("sessions.resolve",{sessionKey:i}),{loaded:!0}}async rename(e,t,i){const s=await this.requireExistingOpenClawStoreKey(e,i);return await this.gateway().request("sessions.patch",{key:s,label:t}),{title:t}}async delete(e,t){const i=await this.requireExistingOpenClawStoreKey(e,t);return await this.gateway().request("sessions.delete",{key:i}),{deleted:!0}}async setMode(e,t,i){const s=openClawSessionModePatch(t),n=await this.requireExistingOpenClawStoreKey(e,i);if(!isAuthoritativeSetModeAck(await this.gateway().request("sessions.patch",{key:n,...s}),n,s))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch did not acknowledge the exact target and effective mode");return{mode:t}}async resolveAgentDefaultModel(e,t){const i=runtimeAgentId(this.liveSessionKeys?(await this.resolveSetModelKey(e,t)).key:await this.requireExistingOpenClawKey(e,t)),s=await this.gateway().request("agents.list",{}),n=s?.agents?.find(e=>e.id===i),o=n?.defaultModel??n?.model;if("string"!=typeof o||!o.trim()||o.trim()!==o)return null;const r=this.modelRefs().toPlatform(o),a=r.indexOf("/");return{modelId:r,...a>0?{modelProvider:r.slice(0,a)}:{}}}async setModel(e,t,i,s){const n=null===t?null:this.modelRefs().toOpenClaw(t),o=s?.attempt.operationId;let r=await modelSwitchTimingSpan(o,"resolve_set_model_key",async()=>await this.resolveSetModelKey(e,s));const a=await(this.modelMutations?.acquireInternal({sessionKey:r.key,label:"session model mutation"}));try{if(a){const t=await this.resolveSetModelKey(e,s);if(t.key!==a.sessionKey)throw new RuntimeCommandError("OUTCOME_UNKNOWN","model target changed while waiting for its physical session gate");r=t}const l=async()=>await modelSwitchTimingSpan(o,"openclaw_sessions_patch",async()=>await this.gateway().request("sessions.patch",{key:r.key,model:n,mustExist:!r.materialized}));let d;try{d=await l()}catch(t){if(r.materialized||!isMissingOpenClawStoreSession(t,r.key))throw t;r=await modelSwitchTimingSpan(o,"materialize_set_model_key",async()=>await this.materializeSetModelKey(e,r.key,s)),d=await l()}const{key:c,materialized:m}=r,u=resolveModelPatch(d,c,t,this.modelRefs()),h=u.effectiveModelId;if(null===t&&(!i||h!==i.modelId))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch effective model does not match the prevalidated agent default");if(m){const e=await modelSwitchTimingSpan(o,"openclaw_sessions_list_post_materialize",async()=>await this.gateway().request("sessions.list",{includeDerivedTitles:!0}));if(!openClawSessionKeysFromList(e).has(c))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch acknowledged a model-intent session that is absent from the authoritative store");if(!this.liveSessionKeys?.registerMaterializedModelKey)throw new RuntimeCommandError("OUTCOME_UNKNOWN","model-intent session registration authority is unavailable");this.liveSessionKeys.registerMaterializedModelKey(c)}return u}finally{this.modelMutations?.releaseBeforeRuntime(a)}}async prepareNativeModel(e,t,i,s,n){await i();const o=void 0===t?null:this.modelRefs().toOpenClaw(t),r=void 0===s?void 0:openClawSessionModePatch(s),a=void 0===n?void 0:openClawPermissionModePatch(n),l=await this.gateway().request("sessions.patch",{key:e,model:o,...r,...a});await i();const d=l;if(!0!==d?.ok||d.key!==e)throw new RuntimeCommandError("OUTCOME_UNKNOWN","native model patch did not acknowledge the exact qualified session");if(r&&!isAuthoritativeSetModeAck(l,e,r))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch did not acknowledge the exact target and effective mode");if(a&&!isAuthoritativePermissionModeAck(l,a))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch did not acknowledge the effective permission mode");return resolveModelPatch(l,e,t??null,this.modelRefs())}async projectCommittedSetModel(e,t){if(!this.projectModel)throw new Error("session metadata projection is unavailable");const i=await this.requireExistingOpenClawKey(e);await this.projectModel(i,t)}async requireExistingOpenClawKey(e,t){if(await(this.liveSessionKeys?.waitForHydration?.()),!this.liveSessionKeys)return e;try{return this.liveSessionKeys.resolveLiveKey(e)}catch(i){if(!t||!isMissingLiveSession(i)||!this.liveSessionKeys.reconcileMissingLiveKey)throw i;return await this.liveSessionKeys.reconcileMissingLiveKey(e,t),this.liveSessionKeys.resolveLiveKey(e)}}async requireExistingOpenClawStoreKey(e,t){const i=await this.requireExistingOpenClawKey(e,t),s=await this.gateway().request("sessions.list",{includeDerivedTitles:!0});if(!openClawSessionKeysFromList(s).has(i))throw new RuntimeCommandError("INVALID_PARAMS",`OpenClaw session key does not exist (refusing sessions.patch upsert): ${i}`);return i}async resolveSetModelKey(e,t){let i;const s="service"===t?.attempt.origin&&void 0!==t.attempt.verifiedMessageGrant;try{i=await this.requireExistingOpenClawKey(e,t)}catch(e){if(!(s&&t&&isMissingLiveSession(e)&&this.liveSessionKeys?.materializeMissingModelKey&&this.liveSessionKeys.registerMaterializedModelKey))throw e}return i?{key:i,materialized:!1}:await this.materializeSetModelKey(e,void 0,t)}async materializeSetModelKey(e,t,i){if(!("service"===i?.attempt.origin&&void 0!==i.attempt.verifiedMessageGrant&&i&&this.liveSessionKeys?.materializeMissingModelKey&&this.liveSessionKeys.registerMaterializedModelKey)){throw new RuntimeCommandError("INVALID_PARAMS",`OpenClaw session key does not exist (refusing sessions.patch upsert): ${t??e}`)}const s=await this.liveSessionKeys.materializeMissingModelKey(e,i);if(t&&s!==t)throw new RuntimeCommandError("INVALID_PARAMS","verified model-intent key conflicts with the live local session");return{key:s,materialized:!0}}gateway(){const e=this.getGateway();if(!e?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected");return e}}function isMissingLiveSession(e){return e instanceof RuntimeCommandError&&"INVALID_PARAMS"===e.code&&e.message.startsWith("no live local session")}function isMissingOpenClawStoreSession(e,t){return e instanceof Error&&e.message===`No session found: ${t}`}function resolveModelPatch(e,t,i,s){const n=null===i?null:s.toOpenClaw(i);if(!isAuthoritativeSetModelAck(e,t,n,i))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch did not acknowledge the exact existing session and effective model");const o=e.resolved.model,r=s.toPlatform(o);if(null!==i&&r!==i&&o!==n)throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch effective model does not match the requested override");return{...null===i?{}:{modelOverrideId:i},effectiveModelId:r,...e.resolved.modelProvider?{modelProvider:e.resolved.modelProvider}:{}}}function runtimeAgentId(e){const t=e.split(":");return"agent"===t[0]&&t[1]?.trim()?t[1]:"main"}export function openClawSessionModePatch(e){const t="verbose"===e||"detailed"===e?{verboseLevel:"full"}:"reasoning"===e||"thinking"===e?{reasoningLevel:"stream"}:"concise"===e||"default"===e?{verboseLevel:"default"}:null;if(!t)throw new RuntimeCommandError("INVALID_PARAMS",`unsupported session mode: ${e}`);return t}export function openClawPermissionModePatch(e){return{permissionMode:"full"===e?"full":null}}function isAuthoritativePermissionModeAck(e,t){const i=e?.entry;return!(!i||"object"!=typeof i||Array.isArray(i))&&i.permissionMode===(t.permissionMode??void 0)}function isAuthoritativeSetModeAck(e,t,i){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const s=e;if(!0!==s.ok||s.key!==t||!s.entry||"object"!=typeof s.entry||Array.isArray(s.entry))return!1;const n=s.entry;return"verboseLevel"in i?n.verboseLevel===i.verboseLevel:n.reasoningLevel===i.reasoningLevel}function isAuthoritativeSetModelAck(e,t,i,s){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const n=e;if("ok"in n&&!0!==n.ok)return!1;if("key"in n&&n.key!==t)return!1;const o=n.resolved;if(!o||"object"!=typeof o||Array.isArray(o))return!1;const r=o.model;return"string"==typeof r&&0!==r.length&&r.trim()===r&&(null!==s||null===i)}export class LocalConversationHistoryBoundary{onCommit;kind="local";intents=new Map;convergence=new Map;generations=new Map;constructor(e){this.onCommit=e}currentGeneration(e){return String(this.generations.get(e)??0n)}async prepare(e){const t=this.intents.get(e.operationId);if(t)return t;const i=(new Date).toISOString(),s=this.generations.get(e.conversationId)??0n,n={intentId:randomUUID(),operationId:e.operationId,conversationId:e.conversationId,state:"prepared",runtimeEpoch:e.runtimeEpoch,fromGeneration:String(s),toGeneration:String(s+1n),boundaryOffset:"0",version:"1",createdAt:i,updatedAt:i};return this.intents.set(e.operationId,n),n}async markResetting(e,t){return this.assertState(e,"prepared"),this.convergence.set(e.operationId,t),this.transition(e,"resetting")}async loadRunConvergence(e){const t=this.convergence.get(e.operationId);if(!t)throw new Error("local clear convergence receipt missing");return t}async markResetDone(e){return this.assertState(e,"resetting"),this.transition(e,"reset_done")}async commit(e){return this.assertState(e,"reset_done"),await(this.onCommit?.(e.conversationId)),this.generations.set(e.conversationId,BigInt(e.toGeneration)),this.transition(e,"committed")}async abortPrepared(e){this.assertState(e,"prepared"),this.transition(e,"aborted")}transition(e,t){const i={...e,state:t,version:String(BigInt(e.version)+1n),updatedAt:(new Date).toISOString()};return this.intents.set(e.operationId,i),i}assertState(e,t){const i=this.intents.get(e.operationId);if(!i||i.version!==e.version||i.state!==t)throw new Error(`local clear transition fenced: expected ${t}`)}}
1
+ import{randomUUID}from"node:crypto";import{performance}from"node:perf_hooks";import{RuntimeCommandError}from"./runtime-command-dispatcher.js";import{ClearConversationUseCase}from"./conversation-clear-use-case.js";import{createSessionModelRefs}from"./openclaw-session-model.js";import{openClawSessionKeysFromList}from"./openclaw-session-key-resolution.js";async function modelSwitchTimingSpan(e,t,i){const s=performance.now();let n="succeeded";try{return await i()}catch(e){throw n="failed",e}finally{e&&process.stderr.write(`[MODEL_SWITCH_TIMING] component=runtime operationId=${e} stage=${t} outcome=${n} durationMs=${(performance.now()-s).toFixed(1)}\n`)}}export class RuntimeSessionControlService{clearRuntime;fallback;activeRuns;constructor(e,t,i){this.clearRuntime=e,this.fallback=t,this.activeRuns=i}async execute(e,t,i){const s=e;if("session.clear"===s.name){if(!i.historyBoundary)throw new RuntimeCommandError("INVALID_PARAMS",`${i.origin} clear attempt lacks its history boundary`);const e=s.context.conversation;return await new ClearConversationUseCase(i.historyBoundary,this.clearRuntime).execute({operationId:s.context.operationId,conversationId:e.runtimeConversationUid,runtimeSessionKey:e.runtimeSessionKey,runtimeEpoch:s.context.dispatchEpoch})}if("session.cancel"===s.name){if(!this.activeRuns)throw new RuntimeCommandError("INVALID_PARAMS","session.cancel active-run authority is not configured");const e=s;return await this.activeRuns.cancelExact({runtimeConversationUid:e.context.conversation.runtimeConversationUid,runtimeSessionKey:e.context.conversation.runtimeSessionKey,historyGeneration:e.context.historyGeneration,targetMessageId:e.params.targetMessageId}),{}}if(!this.fallback)throw new Error(`session command delegate is not configured: ${s.name}`);return await this.fallback.execute(s,t,i)}async projectCommitted(e,t,i){return await(this.fallback?.projectCommitted?.(e,t,i))}}export class OpenClawSessionMetadataService{metadata;constructor(e){this.metadata="function"==typeof e?new OpenClawGatewaySessionMetadataPort(e):e}async execute(e,t,i){const s=e,n=s.context.conversation.runtimeSessionKey,o={lease:t,attempt:i};switch(s.name){case"session.load":return await this.metadata.load(n,o);case"session.rename":{const e=s.params.title.slice(0,64);return await this.metadata.rename(n,e,o)}case"session.delete":return await this.metadata.delete(n,o);case"session.setMode":{const e=s.params.mode;return await this.metadata.setMode(n,e,o)}case"session.setModel":{const e=s.params.modelOverrideId;if(null!==e&&("string"!=typeof e||0===e.length||e.trim()!==e))throw new RuntimeCommandError("INVALID_PARAMS","modelOverrideId must be a non-empty string or null");const t=null===e?await modelSwitchTimingSpan(i.operationId,"resolve_agent_default_model",async()=>await this.metadata.resolveAgentDefaultModel(n,o)):void 0;if(null===e&&!t)throw new RuntimeCommandError("AGENT_DEFAULT_MODEL_NOT_CONFIGURED","agent default model is not configured");return await modelSwitchTimingSpan(i.operationId,"metadata_set_model",async()=>await this.metadata.setModel(n,e,t??void 0,o))}}throw new RuntimeCommandError("INVALID_PARAMS","unsupported session metadata command")}async projectCommitted(e,t,i){if("session.setModel"!==e.name)return;const s=e,n=t;if(this.metadata.projectCommittedSetModel&&await this.metadata.projectCommittedSetModel(s.context.conversation.runtimeSessionKey,n),"service"===i?.origin){if(!i.sessionModelProjection)throw new Error("Message Service session model projection is unavailable");return Object.freeze({state:"pending",revision:"0"})}if(!this.metadata.projectCommittedSetModel)throw new Error("session metadata projection is unavailable")}}export class OpenClawGatewaySessionMetadataPort{getGateway;projectModel;liveSessionKeys;modelMutations;constructor(e,t,i,s){this.getGateway=e,this.projectModel=t,this.liveSessionKeys=i,this.modelMutations=s}modelRefs(){return createSessionModelRefs()}async load(e,t){const i=await this.requireExistingOpenClawKey(e,t);return await this.gateway().request("sessions.resolve",{sessionKey:i}),{loaded:!0}}async rename(e,t,i){const s=await this.requireExistingOpenClawStoreKey(e,i);return await this.gateway().request("sessions.patch",{key:s,label:t}),{title:t}}async delete(e,t){const i=await this.requireExistingOpenClawStoreKey(e,t);return await this.gateway().request("sessions.delete",{key:i}),{deleted:!0}}async setMode(e,t,i){const s=openClawSessionModePatch(t),n=await this.requireExistingOpenClawStoreKey(e,i);if(!isAuthoritativeSetModeAck(await this.gateway().request("sessions.patch",{key:n,...s}),n,s))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch did not acknowledge the exact target and effective mode");return{mode:t}}async resolveAgentDefaultModel(e,t){const i=runtimeAgentId(this.liveSessionKeys?(await this.resolveSetModelKey(e,t)).key:await this.requireExistingOpenClawKey(e,t)),s=await this.gateway().request("agents.list",{}),n=s?.agents?.find(e=>e.id===i),o=n?.defaultModel??n?.model;if("string"!=typeof o||!o.trim()||o.trim()!==o)return null;const r=this.modelRefs().toPlatform(o),a=r.indexOf("/");return{modelId:r,...a>0?{modelProvider:r.slice(0,a)}:{}}}async setModel(e,t,i,s){const n=null===t?null:this.modelRefs().toOpenClaw(t),o=s?.attempt.operationId;let r=await modelSwitchTimingSpan(o,"resolve_set_model_key",async()=>await this.resolveSetModelKey(e,s));const a=await(this.modelMutations?.acquireInternal({sessionKey:r.key,label:"session model mutation"}));try{if(a){const t=await this.resolveSetModelKey(e,s);if(t.key!==a.sessionKey)throw new RuntimeCommandError("OUTCOME_UNKNOWN","model target changed while waiting for its physical session gate");r=t}const l=async()=>await modelSwitchTimingSpan(o,"openclaw_sessions_patch",async()=>await this.gateway().request("sessions.patch",{key:r.key,model:n,mustExist:!r.materialized}));let d;try{d=await l()}catch(t){if(r.materialized||!isMissingOpenClawStoreSession(t,r.key))throw t;r=await modelSwitchTimingSpan(o,"materialize_set_model_key",async()=>await this.materializeSetModelKey(e,r.key,s)),d=await l()}const{key:c,materialized:m}=r,u=resolveModelPatch(d,c,t,this.modelRefs()),h=u.effectiveModelId;if(null===t&&(!i||h!==i.modelId))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch effective model does not match the prevalidated agent default");if(m){const e=await modelSwitchTimingSpan(o,"openclaw_sessions_list_post_materialize",async()=>await this.gateway().request("sessions.list",{includeDerivedTitles:!0}));if(!openClawSessionKeysFromList(e).has(c))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch acknowledged a model-intent session that is absent from the authoritative store");if(!this.liveSessionKeys?.registerMaterializedModelKey)throw new RuntimeCommandError("OUTCOME_UNKNOWN","model-intent session registration authority is unavailable");this.liveSessionKeys.registerMaterializedModelKey(c)}return u}finally{this.modelMutations?.releaseBeforeRuntime(a)}}async prepareNativeModel(e,t,i,s,n){await i();const o=void 0===t?null:this.modelRefs().toOpenClaw(t),r=void 0===s?void 0:openClawSessionModePatch(s),a=openClawPermissionModePatch(n),l=await this.gateway().request("sessions.patch",{key:e,model:o,...r,...a});await i();const d=l;if(!0!==d?.ok||d.key!==e)throw new RuntimeCommandError("OUTCOME_UNKNOWN","native model patch did not acknowledge the exact qualified session");if(r&&!isAuthoritativeSetModeAck(l,e,r))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch did not acknowledge the exact target and effective mode");if(!isAuthoritativePermissionModeAck(l,a))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch did not acknowledge the effective permission mode");return resolveModelPatch(l,e,t??null,this.modelRefs())}async projectCommittedSetModel(e,t){if(!this.projectModel)throw new Error("session metadata projection is unavailable");const i=await this.requireExistingOpenClawKey(e);await this.projectModel(i,t)}async requireExistingOpenClawKey(e,t){if(await(this.liveSessionKeys?.waitForHydration?.()),!this.liveSessionKeys)return e;try{return this.liveSessionKeys.resolveLiveKey(e)}catch(i){if(!t||!isMissingLiveSession(i)||!this.liveSessionKeys.reconcileMissingLiveKey)throw i;return await this.liveSessionKeys.reconcileMissingLiveKey(e,t),this.liveSessionKeys.resolveLiveKey(e)}}async requireExistingOpenClawStoreKey(e,t){const i=await this.requireExistingOpenClawKey(e,t),s=await this.gateway().request("sessions.list",{includeDerivedTitles:!0});if(!openClawSessionKeysFromList(s).has(i))throw new RuntimeCommandError("INVALID_PARAMS",`OpenClaw session key does not exist (refusing sessions.patch upsert): ${i}`);return i}async resolveSetModelKey(e,t){let i;const s="service"===t?.attempt.origin&&void 0!==t.attempt.verifiedMessageGrant;try{i=await this.requireExistingOpenClawKey(e,t)}catch(e){if(!(s&&t&&isMissingLiveSession(e)&&this.liveSessionKeys?.materializeMissingModelKey&&this.liveSessionKeys.registerMaterializedModelKey))throw e}return i?{key:i,materialized:!1}:await this.materializeSetModelKey(e,void 0,t)}async materializeSetModelKey(e,t,i){if(!("service"===i?.attempt.origin&&void 0!==i.attempt.verifiedMessageGrant&&i&&this.liveSessionKeys?.materializeMissingModelKey&&this.liveSessionKeys.registerMaterializedModelKey)){throw new RuntimeCommandError("INVALID_PARAMS",`OpenClaw session key does not exist (refusing sessions.patch upsert): ${t??e}`)}const s=await this.liveSessionKeys.materializeMissingModelKey(e,i);if(t&&s!==t)throw new RuntimeCommandError("INVALID_PARAMS","verified model-intent key conflicts with the live local session");return{key:s,materialized:!0}}gateway(){const e=this.getGateway();if(!e?.isConnected)throw new RuntimeCommandError("GATEWAY_NOT_CONNECTED","Gateway not connected");return e}}function isMissingLiveSession(e){return e instanceof RuntimeCommandError&&"INVALID_PARAMS"===e.code&&e.message.startsWith("no live local session")}function isMissingOpenClawStoreSession(e,t){return e instanceof Error&&e.message===`No session found: ${t}`}function resolveModelPatch(e,t,i,s){const n=null===i?null:s.toOpenClaw(i);if(!isAuthoritativeSetModelAck(e,t,n,i))throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch did not acknowledge the exact existing session and effective model");const o=e.resolved.model,r=s.toPlatform(o);if(null!==i&&r!==i&&o!==n)throw new RuntimeCommandError("OUTCOME_UNKNOWN","sessions.patch effective model does not match the requested override");return{...null===i?{}:{modelOverrideId:i},effectiveModelId:r,...e.resolved.modelProvider?{modelProvider:e.resolved.modelProvider}:{}}}function runtimeAgentId(e){const t=e.split(":");return"agent"===t[0]&&t[1]?.trim()?t[1]:"main"}export function openClawSessionModePatch(e){const t="verbose"===e||"detailed"===e?{verboseLevel:"full"}:"reasoning"===e||"thinking"===e?{reasoningLevel:"stream"}:"concise"===e||"default"===e?{verboseLevel:"default"}:null;if(!t)throw new RuntimeCommandError("INVALID_PARAMS",`unsupported session mode: ${e}`);return t}export function openClawPermissionModePatch(e){return{permissionMode:"full"===e?"full":null}}function isAuthoritativePermissionModeAck(e,t){const i=e?.entry;return!(!i||"object"!=typeof i||Array.isArray(i))&&i.permissionMode===(t.permissionMode??void 0)}function isAuthoritativeSetModeAck(e,t,i){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const s=e;if(!0!==s.ok||s.key!==t||!s.entry||"object"!=typeof s.entry||Array.isArray(s.entry))return!1;const n=s.entry;return"verboseLevel"in i?n.verboseLevel===i.verboseLevel:n.reasoningLevel===i.reasoningLevel}function isAuthoritativeSetModelAck(e,t,i,s){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const n=e;if("ok"in n&&!0!==n.ok)return!1;if("key"in n&&n.key!==t)return!1;const o=n.resolved;if(!o||"object"!=typeof o||Array.isArray(o))return!1;const r=o.model;return"string"==typeof r&&0!==r.length&&r.trim()===r&&(null!==s||null===i)}export class LocalConversationHistoryBoundary{onCommit;kind="local";intents=new Map;convergence=new Map;generations=new Map;constructor(e){this.onCommit=e}currentGeneration(e){return String(this.generations.get(e)??0n)}async prepare(e){const t=this.intents.get(e.operationId);if(t)return t;const i=(new Date).toISOString(),s=this.generations.get(e.conversationId)??0n,n={intentId:randomUUID(),operationId:e.operationId,conversationId:e.conversationId,state:"prepared",runtimeEpoch:e.runtimeEpoch,fromGeneration:String(s),toGeneration:String(s+1n),boundaryOffset:"0",version:"1",createdAt:i,updatedAt:i};return this.intents.set(e.operationId,n),n}async markResetting(e,t){return this.assertState(e,"prepared"),this.convergence.set(e.operationId,t),this.transition(e,"resetting")}async loadRunConvergence(e){const t=this.convergence.get(e.operationId);if(!t)throw new Error("local clear convergence receipt missing");return t}async markResetDone(e){return this.assertState(e,"resetting"),this.transition(e,"reset_done")}async commit(e){return this.assertState(e,"reset_done"),await(this.onCommit?.(e.conversationId)),this.generations.set(e.conversationId,BigInt(e.toGeneration)),this.transition(e,"committed")}async abortPrepared(e){this.assertState(e,"prepared"),this.transition(e,"aborted")}transition(e,t){const i={...e,state:t,version:String(BigInt(e.version)+1n),updatedAt:(new Date).toISOString()};return this.intents.set(e.operationId,i),i}assertState(e,t){const i=this.intents.get(e.operationId);if(!i||i.version!==e.version||i.state!==t)throw new Error(`local clear transition fenced: expected ${t}`)}}
@@ -1 +1 @@
1
- export const SESSION_RECOVERY_REQUIRED_CODE="session_recovery_required";export const SESSION_RECOVERY_REQUIRED_MESSAGE="The session could not be proven safe for another write. Recovery is required.";const DEFAULT_OBSERVATION_TIMEOUT_MS=13e4,DEFAULT_CANCELLATION_QUIESCENCE_TIMEOUT_MS=3e4,DEFAULT_CANCELLATION_TERMINAL_OBSERVATION_TIMEOUT_MS=3e4;export class SessionMutationCoordinator{gate;quiescence;logger;states=new Map;observationTimeoutMs;cancellationQuiescenceTimeoutMs;cancellationTerminalObservationTimeoutMs;setTimer;clearTimer;onRecoveryRequired;constructor(e,r,o,t={}){this.gate=e,this.quiescence=r,this.logger=o,this.observationTimeoutMs=t.observationTimeoutMs??13e4,this.cancellationQuiescenceTimeoutMs=t.cancellationQuiescenceTimeoutMs??3e4,this.cancellationTerminalObservationTimeoutMs=t.cancellationTerminalObservationTimeoutMs??3e4,this.setTimer=t.setTimer??((e,r)=>{const o=setTimeout(e,r);return o.unref?.(),o}),this.clearTimer=t.clearTimer??(e=>clearTimeout(e)),this.onRecoveryRequired=t.onRecoveryRequired}tryStartInteractive(e,r){const o=this.gate.tryStartInteractive(e);return o.ok&&this.register(o.token,r,"attachable"),o}tryStartExclusive(e,r){const o=this.gate.tryStartInteractive(e);return o.ok&&this.register(o.token,r,"exclusive"),o}async acquireInternal(e){const r=await this.gate.acquireInternal(e);return this.register(r,"internal","attachable"),r}bindRun(e,r){if(!e||!r)return;const o=this.states.get(e.operationId);o&&"recovery_required"!==o.phase&&(o.runId=r)}attachOrStartExternal(e,r,o){const t=this.stateForSession(e.sessionKey);if(t)return t.recovery?{ok:!1,reason:"session_busy",recovery:t.recovery}:"exclusive"===t.attachmentPolicy?{ok:!1,reason:"session_busy"}:t.runId&&o&&t.runId!==o?{ok:!1,reason:"run_conflict",recovery:this.fence(t,"run_conflict")}:(!t.runId&&o&&(t.runId=o),{ok:!0,ownership:"observer",ownerOperationId:t.token.operationId,attached:!0});const n=this.tryStartInteractive(e,r);return n.ok?(o&&this.bindRun(n.token,o),{ok:!0,ownership:"owner",token:n.token,attached:!1}):{ok:!1,reason:n.reason}}requestCancel(e,r){const o=this.requireState(e);if(o.recovery)return Promise.resolve({ok:!1,recovery:o.recovery});if(o.cancelPromise)return o.cancelPromise;if(o.terminalKind||o.convergencePromise)return Promise.resolve({ok:!1,reason:"terminal_already_observed"});o.phase="cancelling",o.cancelObservationPromise=new Promise(e=>{o.resolveCancelObservation=e});const t=o.cancelObservationPromise;o.cancelObservationTimer=this.setTimer(()=>{o.terminalKind||o.recovery||this.fence(o,"terminal_not_observed")},this.cancellationTerminalObservationTimeoutMs);const n=Promise.resolve().then(()=>r.chatAbort(e.sessionKey,o.runId)).then(r=>(r.aborted?(o.cancelAcknowledged=!0,this.logger.debug?.(`[session-mutation] remote abort acknowledged sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${o.runId??""} returnedRunIds=${r.runIds.join(",")}`)):this.logger.debug?.(`[session-mutation] remote abort not acknowledged; continuing terminal/quiescence convergence sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${o.runId??""} returnedRunIds=${r.runIds.join(",")}`),o.recovery?{ok:!1,recovery:o.recovery}:{ok:!0,acknowledged:r.aborted,terminalObservation:t})).catch(r=>(this.logger.error?.(`[session-mutation] remote abort transport failed; continuing terminal/quiescence convergence sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${o.runId??""} error=${r instanceof Error?r.message:String(r)}`),o.recovery?{ok:!1,recovery:o.recovery}:{ok:!0,acknowledged:!1,terminalObservation:t}));return o.cancelPromise=n,n}requestCancelForSession(e,r){const o=this.stateForSession(e);return o?"exclusive"===o.attachmentPolicy?Promise.resolve({ok:!1,reason:"not_cancellable"}):this.requestCancel(o.token,r):Promise.resolve({ok:!1,reason:"no_active_mutation"})}converge(e,r){const o=this.requireState(e);return o.convergencePromise||(o.terminalKind=r,this.resolveTerminalObserved(o),o.convergencePromise=this.proveConvergence(o)),o.convergencePromise}release(e){const r=this.states.get(e.operationId);r&&r.token.sessionKey===e.sessionKey&&"ready_to_release"===r.phase&&(this.clearCancellationTimer(r),this.gate.release(r.token),this.states.delete(e.operationId))}releaseBeforeRuntime(e){if(!e)return;const r=this.states.get(e.operationId);r?.recovery||(r&&this.clearCancellationTimer(r),this.gate.release(e),this.states.delete(e.operationId))}requireRecovery(e,r,o){const t=this.requireState(e);return o&&(t.terminalKind=o),this.fence(t,r)}snapshot(e){for(const r of this.states.values())if(r.token.sessionKey===e)return{sessionKey:e,operationId:r.token.operationId,surface:r.surface,attachmentPolicy:r.attachmentPolicy,phase:r.phase,...r.runId?{runId:r.runId}:{},...r.terminalKind?{terminalKind:r.terminalKind}:{},cancelAcknowledged:r.cancelAcknowledged,...r.recovery?{recovery:r.recovery}:{}}}gateSnapshot(e){return this.gate.snapshot(e)}fenceActive(e){for(const r of this.states.values())r.recovery||this.fence(r,e)}register(e,r,o){this.states.set(e.operationId,{token:e,surface:r,attachmentPolicy:o,phase:"active",cancelAcknowledged:!1})}requireState(e){const r=this.states.get(e.operationId);if(!r||r.token.sessionKey!==e.sessionKey)throw new Error("session_mutation_not_registered");return r}stateForSession(e){for(const r of this.states.values())if(r.token.sessionKey===e)return r}async proveConvergence(e){if(e.recovery)return{ok:!1,recovery:e.recovery};if(!e.terminalKind)return{ok:!1,recovery:this.fence(e,"terminal_not_observed")};if(e.recovery)return{ok:!1,recovery:e.recovery};e.phase="awaiting_quiescence";const r="cancelled"===e.terminalKind?this.cancellationQuiescenceTimeoutMs:this.observationTimeoutMs;let o;try{o=await this.quiescence.waitForQuiescence(e.token.sessionKey,r)}catch{return e.recovery?{ok:!1,recovery:e.recovery}:{ok:!1,recovery:this.fence(e,"quiescence_failed")}}if(e.recovery)return{ok:!1,recovery:e.recovery};if(!o.quiescent){const r="session_unresolved"===o.reason?"session_unresolved":"quiescence_timeout";return{ok:!1,recovery:this.fence(e,r,o)}}return e.phase="ready_to_release",this.logger.debug?.(`[session-mutation] convergence proven sessionKey=${e.token.sessionKey} operationId=${e.token.operationId} terminal=${e.terminalKind} elapsed_ms=${o.elapsedMs}`),{ok:!0,permit:{sessionKey:e.token.sessionKey,operationId:e.token.operationId}}}fence(e,r,o){if(e.recovery)return e.recovery;const t={code:"session_recovery_required",sessionKey:e.token.sessionKey,operationId:e.token.operationId,reason:r,surface:e.surface,...e.terminalKind?{terminalKind:e.terminalKind}:{},...o?{quiescence:o}:{}};e.recovery=t,e.phase="recovery_required",this.clearCancellationTimer(e),e.resolveCancelObservation?.({ok:!1,recovery:t}),e.resolveCancelObservation=void 0,this.logger.error?.(`[session-mutation] recovery required sessionKey=${t.sessionKey} operationId=${t.operationId} reason=${t.reason} surface=${t.surface}`);try{this.onRecoveryRequired?.(t)}catch(e){this.logger.error?.(`[session-mutation] recovery restart scheduling failed sessionKey=${t.sessionKey} operationId=${t.operationId} error=${e instanceof Error?e.message:String(e)}`)}return t}resolveTerminalObserved(e){e.recovery||(this.clearCancellationTimer(e),e.resolveCancelObservation?.({ok:!0,terminalObserved:!0}),e.resolveCancelObservation=void 0)}clearCancellationTimer(e){e.cancelObservationTimer&&(this.clearTimer(e.cancelObservationTimer),e.cancelObservationTimer=void 0)}}
1
+ export const SESSION_RECOVERY_REQUIRED_CODE="session_recovery_required";export const SESSION_RECOVERY_REQUIRED_MESSAGE="The session could not be proven safe for another write. Recovery is required.";const DEFAULT_OBSERVATION_TIMEOUT_MS=13e4,DEFAULT_CANCELLATION_QUIESCENCE_TIMEOUT_MS=3e4,DEFAULT_CANCELLATION_TERMINAL_OBSERVATION_TIMEOUT_MS=3e4;export class SessionMutationCoordinator{gate;quiescence;logger;states=new Map;observationTimeoutMs;cancellationQuiescenceTimeoutMs;cancellationTerminalObservationTimeoutMs;setTimer;clearTimer;onRecoveryRequired;constructor(e,r,o,t={}){this.gate=e,this.quiescence=r,this.logger=o,this.observationTimeoutMs=t.observationTimeoutMs??13e4,this.cancellationQuiescenceTimeoutMs=t.cancellationQuiescenceTimeoutMs??3e4,this.cancellationTerminalObservationTimeoutMs=t.cancellationTerminalObservationTimeoutMs??3e4,this.setTimer=t.setTimer??((e,r)=>{const o=setTimeout(e,r);return o.unref?.(),o}),this.clearTimer=t.clearTimer??(e=>clearTimeout(e)),this.onRecoveryRequired=t.onRecoveryRequired}tryStartInteractive(e,r){const o=this.gate.tryStartInteractive(e);return o.ok&&this.register(o.token,r,"attachable"),o}tryStartExclusive(e,r){const o=this.gate.tryStartInteractive(e);return o.ok&&this.register(o.token,r,"exclusive"),o}async acquireInternal(e){const r=await this.gate.acquireInternal(e);return this.register(r,"internal","attachable"),r}bindRun(e,r){if(!e||!r)return;const o=this.states.get(e.operationId);o&&"recovery_required"!==o.phase&&(o.runId=r)}attachOrStartExternal(e,r,o){const t=this.stateForSession(e.sessionKey);if(t)return t.recovery?{ok:!1,reason:"session_busy",recovery:t.recovery}:"exclusive"===t.attachmentPolicy?{ok:!1,reason:"session_busy"}:t.runId&&o&&t.runId!==o?{ok:!1,reason:"run_conflict",recovery:this.fence(t,"run_conflict")}:(!t.runId&&o&&(t.runId=o),{ok:!0,ownership:"observer",ownerOperationId:t.token.operationId,attached:!0});const n=this.tryStartInteractive(e,r);return n.ok?(o&&this.bindRun(n.token,o),{ok:!0,ownership:"owner",token:n.token,attached:!1}):{ok:!1,reason:n.reason}}requestCancel(e,r){const o=this.requireState(e);if(o.recovery)return Promise.resolve({ok:!1,recovery:o.recovery});if(o.cancelPromise)return o.cancelPromise;if(o.terminalKind||o.convergencePromise)return Promise.resolve({ok:!1,reason:"terminal_already_observed"});o.phase="cancelling",o.cancelObservationPromise=new Promise(e=>{o.resolveCancelObservation=e});const t=o.cancelObservationPromise;o.cancelObservationTimer=this.setTimer(()=>{o.terminalKind||o.recovery||this.fence(o,"terminal_not_observed")},this.cancellationTerminalObservationTimeoutMs);const n=Promise.resolve().then(()=>r.chatAbort(e.sessionKey,o.runId)).then(r=>(r.aborted?(o.cancelAcknowledged=!0,this.logger.debug?.(`[session-mutation] remote abort acknowledged sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${o.runId??""} returnedRunIds=${r.runIds.join(",")}`)):this.logger.debug?.(`[session-mutation] remote abort not acknowledged; continuing terminal/quiescence convergence sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${o.runId??""} returnedRunIds=${r.runIds.join(",")}`),o.recovery?{ok:!1,recovery:o.recovery}:{ok:!0,acknowledged:r.aborted,terminalObservation:t})).catch(r=>(this.logger.error?.(`[session-mutation] remote abort transport failed; continuing terminal/quiescence convergence sessionKey=${e.sessionKey} operationId=${e.operationId} runId=${o.runId??""} error=${r instanceof Error?r.message:String(r)}`),o.recovery?{ok:!1,recovery:o.recovery}:{ok:!0,acknowledged:!1,terminalObservation:t}));return o.cancelPromise=n,n}requestCancelForSession(e,r){const o=this.stateForSession(e);return o?"exclusive"===o.attachmentPolicy?Promise.resolve({ok:!1,reason:"not_cancellable"}):this.requestCancel(o.token,r):Promise.resolve({ok:!1,reason:"no_active_mutation"})}converge(e,r){const o=this.requireState(e);return o.convergencePromise||(o.terminalKind=r,this.resolveTerminalObserved(o),o.convergencePromise=this.proveConvergence(o)),o.convergencePromise}release(e){const r=this.states.get(e.operationId);r&&r.token.sessionKey===e.sessionKey&&"ready_to_release"===r.phase&&(this.clearCancellationTimer(r),this.gate.release(r.token),this.states.delete(e.operationId))}releaseBeforeRuntime(e){if(!e)return;const r=this.states.get(e.operationId);r?.recovery||(r&&this.clearCancellationTimer(r),this.gate.release(e),this.states.delete(e.operationId))}requireRecovery(e,r,o){const t=this.requireState(e);return o&&(t.terminalKind=o),this.fence(t,r)}snapshot(e){for(const r of this.states.values())if(r.token.sessionKey===e)return{sessionKey:e,operationId:r.token.operationId,surface:r.surface,attachmentPolicy:r.attachmentPolicy,phase:r.phase,...r.runId?{runId:r.runId}:{},...r.terminalKind?{terminalKind:r.terminalKind}:{},cancelAcknowledged:r.cancelAcknowledged,...r.recovery?{recovery:r.recovery}:{}}}gateSnapshot(e){return this.gate.snapshot(e)}fenceActive(e){for(const r of this.states.values())r.recovery||this.fence(r,e)}register(e,r,o){this.states.set(e.operationId,{token:e,surface:r,attachmentPolicy:o,phase:"active",cancelAcknowledged:!1})}requireState(e){const r=this.states.get(e.operationId);if(!r||r.token.sessionKey!==e.sessionKey)throw new Error("session_mutation_not_registered");return r}stateForSession(e){for(const r of this.states.values())if(r.token.sessionKey===e)return r}async proveConvergence(e){if(e.recovery)return{ok:!1,recovery:e.recovery};if(!e.terminalKind)return{ok:!1,recovery:this.fence(e,"terminal_not_observed")};if(e.recovery)return{ok:!1,recovery:e.recovery};e.phase="awaiting_quiescence";const r="cancelled"===e.terminalKind?this.cancellationQuiescenceTimeoutMs:this.observationTimeoutMs;let o;try{o=await this.quiescence.waitForQuiescence(e.token.sessionKey,r)}catch{return e.recovery?{ok:!1,recovery:e.recovery}:{ok:!1,recovery:this.fence(e,"quiescence_failed")}}if(e.recovery)return{ok:!1,recovery:e.recovery};if(o.quiescent||"session_unresolved"!==o.reason){if(!o.quiescent)return{ok:!1,recovery:this.fence(e,"quiescence_timeout",o)}}else this.logger.warn?.(`[session-mutation] session store unresolved; releasing without writer proof sessionKey=${e.token.sessionKey} operationId=${e.token.operationId} terminal=${e.terminalKind} elapsed_ms=${o.elapsedMs}`);return e.phase="ready_to_release",this.logger.debug?.(`[session-mutation] convergence proven sessionKey=${e.token.sessionKey} operationId=${e.token.operationId} terminal=${e.terminalKind} elapsed_ms=${o.elapsedMs}`),{ok:!0,permit:{sessionKey:e.token.sessionKey,operationId:e.token.operationId}}}fence(e,r,o){if(e.recovery)return e.recovery;const t={code:"session_recovery_required",sessionKey:e.token.sessionKey,operationId:e.token.operationId,reason:r,surface:e.surface,...e.terminalKind?{terminalKind:e.terminalKind}:{},...o?{quiescence:o}:{}};e.recovery=t,e.phase="recovery_required",this.clearCancellationTimer(e),e.resolveCancelObservation?.({ok:!1,recovery:t}),e.resolveCancelObservation=void 0,this.logger.error?.(`[session-mutation] recovery required sessionKey=${t.sessionKey} operationId=${t.operationId} reason=${t.reason} surface=${t.surface}`);try{this.onRecoveryRequired?.(t)}catch(e){this.logger.error?.(`[session-mutation] recovery restart scheduling failed sessionKey=${t.sessionKey} operationId=${t.operationId} error=${e instanceof Error?e.message:String(e)}`)}return t}resolveTerminalObserved(e){e.recovery||(this.clearCancellationTimer(e),e.resolveCancelObservation?.({ok:!0,terminalObserved:!0}),e.resolveCancelObservation=void 0)}clearCancellationTimer(e){e.cancelObservationTimer&&(this.clearTimer(e.cancelObservationTimer),e.cancelObservationTimer=void 0)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeos-claw",
3
- "version": "0.6.66-cloud.10",
3
+ "version": "0.6.66-cloud.12",
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>",
@@ -88,5 +88,5 @@
88
88
  "fast-uri": ">=3.1.4",
89
89
  "brace-expansion": ">=5.0.9"
90
90
  },
91
- "gitHead": "fd75627ce1c2ae5b8c28f3e83bcc781619b7d495"
91
+ "gitHead": "aa07da239f4bf7a9fdea3e9a39704ea3a3d3a92b"
92
92
  }