@robota-sdk/agent-transport 3.0.0-beta.100

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +1137 -0
  2. package/LICENSE +661 -0
  3. package/README.md +121 -0
  4. package/dist/browser/client.d.ts +2 -0
  5. package/dist/browser/client.js +1 -0
  6. package/dist/browser/index.d.ts +624 -0
  7. package/dist/browser/index.d.ts.map +1 -0
  8. package/dist/browser/index.js +2 -0
  9. package/dist/browser/index.js.map +1 -0
  10. package/dist/browser/observer-messages-4fRQZU2D.d.ts +499 -0
  11. package/dist/browser/observer-messages-4fRQZU2D.d.ts.map +1 -0
  12. package/dist/browser/observer-messages-O8jTPEQO.js +2 -0
  13. package/dist/browser/observer-messages-O8jTPEQO.js.map +1 -0
  14. package/dist/node/index.cjs +1 -0
  15. package/dist/node/index.d.cts +1115 -0
  16. package/dist/node/index.d.cts.map +1 -0
  17. package/dist/node/index.d.ts +1115 -0
  18. package/dist/node/index.d.ts.map +1 -0
  19. package/dist/node/index.js +2 -0
  20. package/dist/node/index.js.map +1 -0
  21. package/dist/node/node.cjs +1 -0
  22. package/dist/node/node.d.cts +317 -0
  23. package/dist/node/node.d.cts.map +1 -0
  24. package/dist/node/node.d.ts +317 -0
  25. package/dist/node/node.d.ts.map +1 -0
  26. package/dist/node/node.js +2 -0
  27. package/dist/node/node.js.map +1 -0
  28. package/package.json +122 -0
  29. package/src/__tests__/__fixtures__/korean-text.json +6 -0
  30. package/src/__tests__/access-token-verifier.test.ts +672 -0
  31. package/src/__tests__/admission.test.ts +130 -0
  32. package/src/__tests__/app-tool-messages.test.ts +463 -0
  33. package/src/__tests__/bearer-resource-gate.test.ts +120 -0
  34. package/src/__tests__/channel-frames.test.ts +215 -0
  35. package/src/__tests__/driver-attribution-capability.test.ts +83 -0
  36. package/src/__tests__/file-transfer.test.ts +281 -0
  37. package/src/__tests__/handoff-chunking.test.ts +261 -0
  38. package/src/__tests__/handoff-manifest.test.ts +52 -0
  39. package/src/__tests__/history-attached-budget.test.ts +216 -0
  40. package/src/__tests__/message-decoders.test.ts +870 -0
  41. package/src/__tests__/outbound-delivery.test.ts +558 -0
  42. package/src/__tests__/peer-message-ledger.test.ts +200 -0
  43. package/src/__tests__/permission-summary.test.ts +18 -0
  44. package/src/__tests__/personal-usage-report.test.ts +193 -0
  45. package/src/__tests__/project-read-messages.test.ts +118 -0
  46. package/src/__tests__/resume-buffer.test.ts +93 -0
  47. package/src/__tests__/session-detail-loop-status.test.ts +169 -0
  48. package/src/__tests__/session-directory-messages.test.ts +322 -0
  49. package/src/__tests__/session-error-classification.test.ts +113 -0
  50. package/src/__tests__/session-event-delivery.test.ts +101 -0
  51. package/src/__tests__/session-history-wire.test.ts +364 -0
  52. package/src/__tests__/session-message-handler-e5.test.ts +99 -0
  53. package/src/__tests__/session-message-handler-observe.test.ts +223 -0
  54. package/src/__tests__/session-message-handler.test.ts +726 -0
  55. package/src/__tests__/session-resume-bridge.test.ts +472 -0
  56. package/src/__tests__/settings-messages.test.ts +193 -0
  57. package/src/__tests__/stdio-carrier.test.ts +85 -0
  58. package/src/__tests__/submit-prompt-shape.test.ts +78 -0
  59. package/src/__tests__/usage-report-carrier.test.ts +98 -0
  60. package/src/__tests__/ws-broadcast-events.test.ts +94 -0
  61. package/src/app-tool-messages.ts +140 -0
  62. package/src/background-messages.ts +169 -0
  63. package/src/channel-frames.ts +182 -0
  64. package/src/client.ts +17 -0
  65. package/src/handoff-chunking.ts +275 -0
  66. package/src/index.ts +135 -0
  67. package/src/loop-control-messages.ts +34 -0
  68. package/src/message-decoders.ts +485 -0
  69. package/src/message-parser.ts +33 -0
  70. package/src/node/access-token-verifier.ts +363 -0
  71. package/src/node/admission.ts +153 -0
  72. package/src/node/bearer-resource-gate.ts +331 -0
  73. package/src/node/file-transfer.ts +530 -0
  74. package/src/node/handoff-manifest.ts +77 -0
  75. package/src/node/index.ts +45 -0
  76. package/src/node/stdio-carrier.ts +59 -0
  77. package/src/observer-messages.ts +47 -0
  78. package/src/open-prompts.ts +76 -0
  79. package/src/outbound-delivery.ts +240 -0
  80. package/src/peer-message-ledger.ts +193 -0
  81. package/src/permission-summary.ts +18 -0
  82. package/src/project-read-messages.ts +74 -0
  83. package/src/protocol-session.ts +56 -0
  84. package/src/resume-buffer.ts +123 -0
  85. package/src/session-directory-messages.ts +162 -0
  86. package/src/session-error-classification.ts +91 -0
  87. package/src/session-events.ts +290 -0
  88. package/src/session-message-handler.ts +413 -0
  89. package/src/session-query-messages.ts +160 -0
  90. package/src/session-resume-bridge.ts +275 -0
  91. package/src/settings-messages.ts +103 -0
  92. package/src/usage-messages.ts +195 -0
  93. package/src/wire-messages.ts +436 -0
@@ -0,0 +1,2 @@
1
+ import{a as e,i as t,n,r,t as i}from"./observer-messages-O8jTPEQO.js";import{isSessionChangeRefusal as a,isSessionDeleteRefusal as o}from"@robota-sdk/agent-interface-session";import{AuthenticationError as s,ModelNotAvailableError as c,NetworkError as l,ProviderError as u,RateLimitError as d}from"@robota-sdk/agent-core";function f(e){return e.type===`call-app-tool`}function p(e){return e.type===`read-app-view`}function m(e){return e?.maxFrameBytes??4193280}function h(e,t){return new TextEncoder().encode(JSON.stringify(e)).byteLength<=t}function g(e,t,n,r,i){let{requestId:a}=n,o=e.readToolAppView(n.viewId);if(o===void 0){t({type:`protocol_error`,message:`This app view is no longer available.`,requestId:a});return}if(!Object.values(o.callableTools).includes(n.name)){t({type:`protocol_error`,message:`This tool is not available to this app view.`,requestId:a});return}let{signal:s}=i??{},c=m(i);e.invokeRuntimeTool(n.name,n.arguments,{permissionInteraction:`interactive`,...r?{driverId:r}:{},...s?{signal:s}:{}}).then(e=>{if(s?.aborted)return;let n={type:`app_tool_result`,requestId:a,success:e.success,...e.result===void 0?{}:{data:e.result},...e.parts===void 0?{}:{parts:e.parts},...e.error===void 0?{}:{error:e.error}};h(n,c)?t(n):t({type:`protocol_error`,message:`This app tool result is too large to send.`,requestId:a})},e=>{s?.aborted||t({type:`protocol_error`,message:e instanceof Error?e.message:String(e),requestId:a})})}function _(e,t,n,r){let{requestId:i}=n,a=e.readToolAppView(n.viewId);if(a===void 0){t({type:`protocol_error`,message:`This app view is no longer available.`,requestId:i});return}let o={type:`app_view`,requestId:i,view:a};if(!h(o,m(r))){t({type:`protocol_error`,message:`This app view is too large to send.`,requestId:i});return}t(o)}function v(e,t,n){if(n.type===`get-background-tasks`){t({type:`background_tasks`,tasks:e.listBackgroundTasks(n.filter)});return}if(n.type===`get-background-task`){b(e,t,n);return}if(n.type===`get-background-job-groups`){t({type:`background_job_groups`,groups:e.listBackgroundJobGroups()});return}if(n.type===`get-background-job-group`){S(e,t,n);return}if(n.type===`wait-background-job-group`){C(e,t,n);return}x(e,t,n)}function y(e,t,n){if(!n.taskId){t({type:`protocol_error`,message:`taskId is required`});return}if(n.type===`cancel-background-task`){T(t,`cancel`,n.taskId,e.cancelBackgroundTask(n.taskId,n.reason));return}if(n.type===`close-background-task`){T(t,`close`,n.taskId,e.closeBackgroundTask(n.taskId));return}w(e,t,n)}function b(e,t,n){if(!n.taskId){t({type:`protocol_error`,message:`taskId is required`});return}t({type:`background_task`,taskId:n.taskId,task:e.getBackgroundTask(n.taskId)??null})}function x(e,t,n){if(!n.taskId){t({type:`protocol_error`,message:`taskId is required`});return}e.readBackgroundTaskLog(n.taskId,n.cursor).then(e=>t({type:`background_task_log`,taskId:n.taskId,page:e}),e=>t({type:`protocol_error`,message:e.message}))}function S(e,t,n){if(!n.groupId){t({type:`protocol_error`,message:`groupId is required`});return}t({type:`background_job_group`,groupId:n.groupId,group:e.getBackgroundJobGroup(n.groupId)??null})}function C(e,t,n){if(!n.groupId){t({type:`protocol_error`,message:`groupId is required`});return}e.waitBackgroundJobGroup(n.groupId).then(e=>t({type:`background_job_group`,groupId:n.groupId,group:e}),e=>t({type:`protocol_error`,message:e.message}))}function w(e,t,n){if(!n.input){t({type:`protocol_error`,message:`input is required`});return}T(t,`send`,n.taskId,e.sendBackgroundTask(n.taskId,n.input))}function T(e,t,n,r){r.then(()=>e({type:`background_task_control_result`,action:t,taskId:n,success:!0}),r=>e({type:`background_task_control_result`,action:t,taskId:n,success:!1,message:r.message}))}function E(e){return e.type===`stop-waiting-loop`}function D(e,t,n){let{requestId:r}=n;e.stopWaitingSelfPacedLoop().then(e=>t({type:`waiting_loop_stop`,requestId:r,outcome:e}),e=>t({type:`waiting_loop_stop`,requestId:r,outcome:{kind:`failed`,message:O(e)}}))}function O(e){return e instanceof Error?e.message:String(e)}function k(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function A(e){return typeof e==`string`&&e.length>0?e:void 0}function j(e){if(!(new TextEncoder().encode(e).byteLength>4194304))try{let t=JSON.parse(e);if(!k(t))return;let n=A(t.requestId);if(!n)return;if(t.type===`get-personal-usage-report`)return{type:`personal`,requestId:n};let r=A(t.sessionId);return t.type===`get-stored-session-usage-report`&&r?{type:`stored-session`,requestId:n,sessionId:r}:void 0}catch{return}}function ee(e,t,n,r){return n.type===`get-personal-usage-report`?(re(t,n,r.personalUsageReporter),!0):n.type===`get-stored-session-usage-report`?(ne(t,n,r.storedSessionUsageReporter),!0):n.type===`get-usage-report`?(te(e,t,r.usageReporter),!0):!1}function te(e,t,n){if(!n){t({type:`protocol_error`,message:`Per-session usage reporting is not available on this host.`});return}try{Promise.resolve(n(e)).then(e=>t({type:`usage_report`,report:e}),e=>t({type:`protocol_error`,message:P(e)}))}catch(e){t({type:`protocol_error`,message:P(e instanceof Error?e:String(e))})}}function ne(e,t,n){if(!n){M(e,t,`not_available`,`Stored-session usage reporting is not available on this host.`);return}try{Promise.resolve(n(t.sessionId)).then(n=>e({type:`stored_session_usage_report`,requestId:t.requestId,sessionId:t.sessionId,report:n}),n=>M(e,t,`report_failed`,P(n)))}catch(n){M(e,t,`report_failed`,P(n instanceof Error?n:String(n)))}}function M(e,t,n,r){e({type:`stored_session_usage_report_error`,requestId:t.requestId,sessionId:t.sessionId,code:n,message:r})}function re(e,t,n){if(!n){N(e,t.requestId,`not_available`,`Personal usage reporting is not available on this host.`);return}try{Promise.resolve(n({period:t.period,timezone:t.timezone})).then(n=>e({type:`personal_usage_report`,requestId:t.requestId,report:n}),n=>N(e,t.requestId,`report_failed`,P(n)))}catch(n){N(e,t.requestId,`report_failed`,P(n instanceof Error?n:String(n)))}}function N(e,t,n,r){e({type:`personal_usage_report_error`,requestId:t,code:n,message:r})}function P(e){return e instanceof Error?e.message:String(e)}function ie(e,n){let i=t(e,r);if(i.ok)return i.message;let a=j(e);return a?.type===`personal`?(n({type:`personal_usage_report_error`,requestId:a.requestId,code:`report_failed`,message:`Invalid personal usage report request.`}),null):a?.type===`stored-session`?(n({type:`stored_session_usage_report_error`,requestId:a.requestId,sessionId:a.sessionId,code:`report_failed`,message:`Invalid stored-session usage report request.`}),null):(n({type:`protocol_error`,message:i.reason}),null)}function ae(e){return e.type===`project-status`||e.type===`project-diff`||e.type===`project-memory`}function F(e,t){e({type:`protocol_error`,message:`This host does not support the Project panel.`,requestId:t})}function oe(e,t,n){let{requestId:r}=n;if(n.type===`project-status`){if(!e.readProjectStatus)return F(t,r);e.readProjectStatus().then(e=>t({type:`project_status`,requestId:r,result:e}),e=>t({type:`project_status`,requestId:r,result:{kind:`failed`,message:e instanceof Error?e.message:String(e)}}))}else if(n.type===`project-diff`){if(!e.readProjectDiff)return F(t,r);e.readProjectDiff(n.path).then(e=>t({type:`project_diff`,requestId:r,result:e}),e=>t({type:`project_diff`,requestId:r,result:{kind:`failed`,message:e instanceof Error?e.message:String(e)}}))}else{if(!e.readProjectMemory)return F(t,r);e.readProjectMemory().then(e=>t({type:`project_memory`,requestId:r,result:e}),e=>t({type:`project_memory`,requestId:r,result:{kind:`unavailable`,message:e instanceof Error?e.message:String(e)}}))}}function se(e){return e.type===`list-sessions`||e.type===`new-session`||e.type===`switch-session`}function ce(e){return e.type===`rename-session`}function le(e){return e.type===`delete-session`}function ue(e,t,n){if(t.type===`list-sessions`){de(e,t.requestId,n);return}let r=t.requestId===void 0?{}:{requestId:t.requestId};if(!n){e({type:`session_change_failed`,code:`not_available`,message:`Sessions cannot be switched on this host.`,...r});return}Promise.resolve().then(()=>t.type===`new-session`?n.newSession():n.switchSession(t.sessionId)).catch(t=>e({type:`session_change_failed`,code:a(t)?t.code:`failed`,message:I(t),...r}))}function de(e,t,n){if(!n){e({type:`sessions_error`,requestId:t,code:`not_available`,message:`Session listing is not available on this host.`});return}try{e({type:`sessions`,requestId:t,listing:n.listSessions()})}catch(n){e({type:`sessions_error`,requestId:t,code:`list_failed`,message:I(n instanceof Error?n:String(n))})}}function I(e){return e instanceof Error?e.message:String(e)}function fe(e,t,n){if(!n){e({type:`session_rename_failed`,requestId:t.requestId,message:`Sessions cannot be renamed on this host.`});return}n.renameSession(t.sessionId,t.name).then(()=>e({type:`session_renamed_in_list`,requestId:t.requestId,sessionId:t.sessionId,name:t.name}),n=>e({type:`session_rename_failed`,requestId:t.requestId,message:I(n)}))}function pe(e,t,n){if(!n){e({type:`session_delete_failed`,requestId:t.requestId,code:`not_available`,message:`Sessions cannot be deleted on this host.`});return}n.deleteSession(t.sessionId).then(()=>e({type:`session_deleted`,requestId:t.requestId,sessionId:t.sessionId}),n=>e({type:`session_delete_failed`,requestId:t.requestId,code:o(n)?n.code:`failed`,message:I(n)}))}const L=new WeakMap;function me(e){let t=L.get(e);t===void 0&&(t={frames:new Map,holders:0},L.set(e,t));let n=t;n.holders+=1;let r=!1;return{record:e=>{r||n.frames.set(e.event.id,e)},forget:e=>{n.frames.delete(e)},clear:()=>{n.frames.clear()},release:()=>{r||(r=!0,--n.holders,n.holders===0&&L.get(e)===n&&L.delete(e))}}}function he(e){return[...L.get(e)?.frames.values()??[]]}function ge(e){let t=[],n=new Set,r=[e];for(let e=0;e<=8&&r.length>0;e++){let e=[];for(let i of r)if(!(i==null||n.has(i))&&(n.add(i),t.push(i),typeof i==`object`)){let t=i;e.push(t.originalError,t.cause)}r=e}return t}function _e(e){let t;for(let n of ge(e)){if(n instanceof s)return{code:`auth`,...n.provider!==void 0&&{provider:n.provider}};if(n instanceof d)return{code:`rate_limit`,...n.provider!==void 0&&{provider:n.provider},...n.retryAfter!==void 0&&{retryAfterSeconds:n.retryAfter}};if(n instanceof c)return{code:`model_unavailable`,provider:n.provider,...n.model!==void 0&&{model:n.model}};if(n instanceof l)return{code:`network`,...n.provider!==void 0&&{provider:n.provider}};t===void 0&&n instanceof u&&(t={code:`provider`,provider:n.provider})}return t??{}}const ve={text_delta:`forwarded`,tool_start:`forwarded`,tool_end:`forwarded`,thinking:`forwarded`,thinking_delta:`non-surface`,complete:`forwarded`,error:`forwarded`,context_update:`forwarded`,compact:`forwarded`,interrupted:`forwarded`,skill_activation:`forwarded`,background_task_event:`forwarded`,background_job_group_event:`forwarded`,execution_workspace_event:`forwarded`,user_message:`forwarded`,turn_source:`forwarded`,context_file_refreshed:`forwarded`,memory_event:`forwarded`,goal_event:`non-surface`,plan_event:`forwarded`,branch_event:`forwarded`,permission_request:`forwarded`,ask_request:`forwarded`,prompt_resolved:`forwarded`,ui_intent:`requester-routed`,session_renamed:`forwarded`,history_cleared:`forwarded`,session_switched:`forwarded`,status_changed:`forwarded`};function ye(e,t,n={}){let r=()=>{let t=e.getActiveDriverId();return t?{driverId:t}:{}},i=e=>t({type:`user_message`,content:e,...r()}),a=e=>t({type:`text_delta`,delta:e,...r()}),o=e=>t({type:`tool_start`,state:e,...r()}),s=e=>t({type:`tool_end`,state:e,...r()}),c=e=>t({type:`thinking`,isThinking:e,...r()}),l=e=>t({type:`complete`,result:R(e),...r()}),u=e=>t({type:`interrupted`,result:R(e),...r()}),d=e=>t({type:`error`,message:e.message,..._e(e),...r()}),f=e=>t({type:`background_task_event`,event:e}),p=e=>t({type:`background_job_group_event`,event:e}),m=e=>t({type:`execution_workspace_event`,snapshot:e.snapshot}),h=e=>t({type:`plan_event`,event:e}),g=e=>t({type:`context_file_refreshed`,event:e}),_=e=>t({type:`branch_event`,event:e}),v=n.receivePrompts??!0,y=v?me(e):void 0,b=e=>{let n={type:`permission_request`,event:e};y?.record(n),t(n)},x=e=>{let n={type:`ask_request`,event:e};y?.record(n),t(n)},S=e=>{y?.forget(e.id),t({type:`prompt_resolved`,event:e})},C=e=>{(e.requesterDriverId===void 0||e.requesterDriverId===n.getSurfaceDriverId?.())&&t({type:`ui_intent`,event:e})},w=e=>t({type:`session_renamed`,event:e}),T=()=>t({type:`history_cleared`}),E=e=>{y?.clear(),t({type:`session_switched`,event:e})},D=e=>t({type:`context`,state:e}),O=()=>t({type:`history_changed`}),k=e=>t({type:`history_changed`,compact:{trigger:e.trigger,beforeUsedPercentage:e.before.usedPercentage,afterUsedPercentage:e.after.usedPercentage}}),A=e=>t({type:`turn_source`,source:e}),j=e=>t({type:`session_status`,status:e});return e.on(`user_message`,i),e.on(`text_delta`,a),e.on(`tool_start`,o),e.on(`tool_end`,s),e.on(`thinking`,c),e.on(`complete`,l),e.on(`interrupted`,u),e.on(`error`,d),e.on(`background_task_event`,f),e.on(`background_job_group_event`,p),e.on(`execution_workspace_event`,m),e.on(`plan_event`,h),e.on(`context_file_refreshed`,g),e.on(`branch_event`,_),v&&(e.on(`permission_request`,b),e.on(`ask_request`,x)),e.on(`prompt_resolved`,S),e.on(`ui_intent`,C),e.on(`session_renamed`,w),e.on(`history_cleared`,T),e.on(`session_switched`,E),e.on(`context_update`,D),e.on(`compact`,k),e.on(`skill_activation`,O),e.on(`memory_event`,O),e.on(`turn_source`,A),e.on(`status_changed`,j),()=>{e.off(`user_message`,i),e.off(`text_delta`,a),e.off(`tool_start`,o),e.off(`tool_end`,s),e.off(`thinking`,c),e.off(`complete`,l),e.off(`interrupted`,u),e.off(`error`,d),e.off(`background_task_event`,f),e.off(`background_job_group_event`,p),e.off(`execution_workspace_event`,m),e.off(`plan_event`,h),e.off(`context_file_refreshed`,g),e.off(`branch_event`,_),v&&(e.off(`permission_request`,b),e.off(`ask_request`,x)),e.off(`prompt_resolved`,S),e.off(`ui_intent`,C),e.off(`session_renamed`,w),e.off(`history_cleared`,T),e.off(`session_switched`,E),e.off(`context_update`,D),e.off(`compact`,k),e.off(`skill_activation`,O),e.off(`memory_event`,O),e.off(`turn_source`,A),e.off(`status_changed`,j),y?.release()}}function R(e){let{history:t,...n}=e;return n}const be=256*1024,xe=new TextEncoder;function Se(e){return e.type===`get-messages`||e.type===`get-history`||e.type===`get-prompts`||e.type===`get-context`||e.type===`get-commands`||e.type===`get-status`||e.type===`list-models`||e.type===`get-agent-definitions`||e.type===`get-executing`||e.type===`get-pending`||e.type===`get-execution-workspace`||e.type===`read-execution-detail`}function Ce(e,t,n){if(n.type===`get-messages`)t({type:`messages`,messages:e.getMessages(),display:e.getMessagesDisplay()});else if(n.type===`get-history`)t(Te(e,n.fromIndex??0));else if(n.type===`get-prompts`)for(let n of he(e))t(n);else if(n.type===`get-context`)t({type:`context`,state:e.getContextState()});else if(n.type===`get-commands`)t({type:`commands`,commands:e.listCommands(),skills:e.listSkills()});else if(n.type===`get-status`)t({type:`session_status`,status:e.getStatusSnapshot()});else if(n.type===`list-models`){let r=e.listModels();t({type:`model_list`,requestId:n.requestId,...r})}else n.type===`get-agent-definitions`?t({type:`agent_definitions`,requestId:n.requestId,agents:e.listAgentDefinitions(),current:e.getDefaultAgentType()}):n.type===`get-executing`?t({type:`executing`,executing:e.isExecuting()}):n.type===`get-execution-workspace`?t({type:`execution_workspace_event`,snapshot:e.getExecutionWorkspaceSnapshot()}):n.type===`read-execution-detail`?we(e,t,n):t(z(e))}function we(e,t,n){let{requestId:r}=n;e.readExecutionWorkspaceDetail(n.entryId,n.cursor).then(e=>t({type:`execution_detail`,requestId:r,page:e}),e=>t({type:`execution_detail_error`,requestId:r,message:e instanceof Error?e.message:String(e)}))}function z(e){return{type:`pending`,pending:e.getPendingPrompt(),pendingCount:e.getPendingCount()}}function Te(e,t){let n=e.getFullHistory(),r=n.length,i=Math.min(t,r),a=[],o=0;for(let e=i;e<r;e+=1){let t=Ee(n[e]),r=xe.encode(JSON.stringify(t)).byteLength;if(a.length>0&&o+r>262144)break;a.push(t),o+=r}return{type:`history`,startIndex:i,total:r,entries:a}}function Ee(e){return{...e,timestamp:De(e.timestamp)}}function De(e){return typeof e==`string`?e:e.toISOString()}function Oe(e){return e.type===`get-settings`||e.type===`update-settings`}function ke(e,t,n,r,i){if(!r){B(t,n.requestId,`not_available`,`Settings are not available on this host.`);return}try{if(n.type===`get-settings`){Promise.resolve(r.getSettings(e,i)).then(e=>t({type:`settings`,requestId:n.requestId,settings:e}),e=>B(t,n.requestId,`update_failed`,V(e)));return}Promise.resolve(r.updateSettings(e,n.patch,i)).then(e=>{e.ok?t({type:`settings`,requestId:n.requestId,settings:e.settings}):B(t,n.requestId,e.code,e.message)},e=>B(t,n.requestId,`update_failed`,V(e)))}catch(e){B(t,n.requestId,`update_failed`,V(e instanceof Error?e:String(e)))}}function B(e,t,n,r){e({type:`settings_error`,requestId:t,code:n,message:r})}function V(e){return e instanceof Error?e.message:String(e)}function Ae(e){let t=e.role??`drive`,n=ye(e.session,e.deliver,{getSurfaceDriverId:()=>e.driverId,receivePrompts:t===`drive`}),r=new AbortController,i=()=>{r.abort(),n()};return{onMessage:je(e.session,e.deliver,e.driverId,{personalUsageReporter:e.personalUsageReporter,usageReporter:e.usageReporter,storedSessionUsageReporter:e.storedSessionUsageReporter},e.surface,t,e.sessionDirectory,e.settingsReporter,e.commandSurfaceLocality,{signal:r.signal,...e.maxFrameBytes===void 0?{}:{maxFrameBytes:e.maxFrameBytes}}),cleanup:i}}function je(e,t,n,r=H,a,o=`drive`,s,c,l,u){return d=>{let f=ie(d,t);if(f){if(o===`observe`&&!i(f.type)){t({type:`protocol_error`,message:`Not permitted for an observer: ${f.type}`});return}U(e,t,f,n,r,a,s,c,l,u)}}}const H={personalUsageReporter:void 0,usageReporter:void 0,storedSessionUsageReporter:void 0};function U(e,t,n,r,i=H,a,o,s,c,l){if(ee(e,t,n,i))return;if(se(n)){ue(t,n,o);return}if(Oe(n)){ke(e,t,n,s,c);return}if(ce(n)){fe(t,n,o);return}if(le(n)){pe(t,n,o);return}if(Me(n)){Le(e,t,n,r,a,c);return}if(E(n)){D(e,t,n);return}if(Se(n)){Ce(e,t,n);return}if(ae(n)){oe(e,t,n);return}if(f(n)){g(e,t,n,r,l);return}if(p(n)){_(e,t,n,l);return}if(Ne(n)){v(e,t,n);return}if(Pe(n)){y(e,t,n);return}if(Fe(n)){Ie(e,n,r);return}let u=n.type;t({type:`protocol_error`,message:`Unknown message type: ${u}`})}function Me(e){return e.type===`submit`||e.type===`command`||e.type===`abort`||e.type===`cancel-queue`}function Ne(e){return e.type===`get-background-tasks`||e.type===`get-background-task`||e.type===`read-background-task-log`||e.type===`get-background-job-groups`||e.type===`get-background-job-group`||e.type===`wait-background-job-group`}function Pe(e){return e.type===`cancel-background-task`||e.type===`close-background-task`||e.type===`send-background-task`}function Fe(e){return e.type===`permission-response`||e.type===`ask-response`}function Ie(e,t,n){t.type===`permission-response`?e.resolvePermission(t.id,t.result,n):e.resolveAsk(t.id,t.response,n)}function Le(e,t,n,r,i,a){if(n.type===`submit`){if(typeof n.prompt!=`string`||n.prompt.length===0){t({type:`protocol_error`,message:`prompt must be a non-empty string`});return}let a={...r?{driverId:r}:{},...i?{surface:i}:{}};e.submit(n.prompt,void 0,void 0,Object.keys(a).length>0?a:void 0).then(()=>t(z(e)),e=>t({type:`protocol_error`,message:e.message}))}else if(n.type===`command`){let i=n.requestId===void 0?{}:{requestId:n.requestId};if(!n.name){t({type:`protocol_error`,message:`name is required`,...i});return}e.executeCommand(n.name,n.args??``,`remote`,r,a).then(e=>{t({type:`command_result`,name:n.name,message:e?.message??`Unknown command: ${n.name}`,success:e?.success??!1,data:e?.data,...i})},e=>{t({type:`protocol_error`,message:e.message,...i})})}else n.type===`abort`?e.abort():e.cancelQueue()}const W=1024*1024,G=8*W,Re=W;function K(e,t=G){return e!==void 0&&e>t}const q=120*1e3;function J(e=q,t=Date.now){let n;return{maxPendingMs:e,observe(r){if(r===void 0)return;if(r===0){n=void 0;return}let i=t();n??=i;let a=i-n;return a>e?a:void 0}}}function Y(e,t,n,r=G,i=J()){let a=!1,o=t=>{if(a)return;let o=n?.();if(K(o,r)){a=!0,s(Error(`outbound backpressure budget exceeded: ${o} byte(s) pending, limit ${r}. The peer has accepted frames it is not reading.`),t.type);return}let c=i.observe(o);if(c!==void 0){a=!0,s(Error(`outbound drain budget exceeded: ${o} byte(s) pending for ${c} ms, limit ${i.maxPendingMs} ms. The peer is reading too slowly to ever drain what it accepted.`),t.type);return}try{e(t)}catch(e){a=!0,s(e instanceof Error?e:Error(String(e)),t.type)}};function s(e,n){try{t(e,n)}catch{}}return Object.assign(o,{pendingBytes:()=>n?.()})}var X=class{frames=[];sizes=[];nextSeq=1;bytes=0;maxFrames;maxBytes;sizeOf;constructor(e={}){this.maxFrames=e.maxFrames??512,this.maxBytes=e.maxBytes??4194304,this.sizeOf=e.sizeOf??(e=>JSON.stringify(e).length)}append(e){let t=this.nextSeq;this.nextSeq+=1;let n=this.sizeOf(e);return this.frames.push({seq:t,message:e}),this.sizes.push(n),this.bytes+=n,this.evict(),t}ackThrough(e){let t=0;for(;t<this.frames.length&&this.frames[t].seq<=e;)t+=1;this.removeOldest(t)}tailAfter(e){return this.frames.length===0?e>=this.nextSeq-1?{kind:`tail`,frames:[]}:{kind:`overrun`}:e<this.frames[0].seq-1?{kind:`overrun`}:{kind:`tail`,frames:this.frames.filter(t=>t.seq>e)}}get lastSeq(){return this.nextSeq-1}get size(){return this.frames.length}evict(){let e=0,t=this.bytes;for(;this.frames.length-e>this.maxFrames||t>this.maxBytes&&this.frames.length-e>1;)t-=this.sizes[e],e+=1;this.removeOldest(e)}removeOldest(e){if(!(e<=0)){for(let t=0;t<e;t+=1)this.bytes-=this.sizes[t]??0;this.frames.splice(0,e),this.sizes.splice(0,e),this.frames.length===0&&(this.bytes=0)}}};const Z=new TextEncoder,ze=new TextDecoder,Q=Z.encode(`RBF`),Be=1,Ve=4294967295;function He(e,t){let n=Z.encode(e);if(n.byteLength===0||n.byteLength>255)throw Error(`channel name must be 1..255 UTF-8 bytes (got ${n.byteLength})`);if(!Number.isInteger(t)||t<0||t>Ve)throw Error(`seq must be an integer in 0..${Ve} (got ${t})`);return n}function Ue(e,t,n,r){e.set(Q,0),e[3]=1,e[4]=t,e[5]=n.byteLength,e.set(n,6);let i=6+n.byteLength;return new DataView(e.buffer,e.byteOffset,e.byteLength).setUint32(i,r,!1),i+4}function We(e){let t=He(e.channel,e.seq),n=new Uint8Array(6+t.byteLength+4+e.payload.byteLength),r=Ue(n,1,t,e.seq);return n.set(e.payload,r),n}function Ge(e){let t=He(e.channel,e.seq),n=Z.encode(JSON.stringify({event:e.event,payload:e.payload})),r=new Uint8Array(6+t.byteLength+4+n.byteLength),i=Ue(r,2,t,e.seq);return r.set(n,i),r}function Ke(e){return e.byteLength<Q.length?!1:Q.every((t,n)=>e[n]===t)}function qe(e){let t=ze.decode(e),n;try{n=JSON.parse(t)}catch{return{ok:!1,error:`channel frame: malformed JSON body`}}if(typeof n!=`object`||!n||Array.isArray(n))return{ok:!1,error:`channel frame: event body must be a JSON object`};let{event:r,payload:i}=n;return typeof r!=`string`||r.length===0?{ok:!1,error:"channel frame: event body is missing a non-empty `event` name"}:{ok:!0,event:r,payload:i??null}}function Je(e){if(!Ke(e))return{ok:!1,error:`channel frame: bad magic (not a channel frame)`};if(e.byteLength<6)return{ok:!1,error:`channel frame: truncated header`};if(e[3]!==1)return{ok:!1,error:`channel frame: unsupported version ${String(e[3])}`};let t=e[4],n=e[5]??0;if(n===0)return{ok:!1,error:`channel frame: empty channel name`};let r=6+n,i=r+4;if(e.byteLength<i)return{ok:!1,error:`channel frame: truncated (header longer than the frame)`};let a=ze.decode(e.subarray(6,r)),o=new DataView(e.buffer,e.byteOffset,e.byteLength).getUint32(r,!1),s=e.subarray(i);if(t===1)return{ok:!0,frame:{kind:`binary`,channel:a,seq:o,payload:Uint8Array.from(s)}};if(t===2){let e=qe(s);return e.ok?{ok:!0,frame:{kind:`event`,channel:a,seq:o,event:e.event,payload:e.payload}}:{ok:!1,error:e.error}}return{ok:!1,error:`channel frame: unknown kind ${String(t)}`}}var Ye=class{session;buffer;driverId;reporters;surface;commandSurfaceLocality;maxFrameBytes;lifetime=new AbortController;unsubscribe;onDeliveryError;outbound;emitBoundary;disposed=!1;holding=!1;constructor(e){this.session=e.session,this.buffer=new X(e.buffer),this.driverId=e.driverId,this.surface=e.surface,this.commandSurfaceLocality=e.commandSurfaceLocality,this.maxFrameBytes=e.maxFrameBytes,this.reporters={personalUsageReporter:e.personalUsageReporter,usageReporter:e.usageReporter,storedSessionUsageReporter:e.storedSessionUsageReporter},this.emitBoundary=Y(e=>this.emit(e),(e,t)=>this.reportDeliveryError(e,t)),this.unsubscribe=ye(this.session,this.emitBoundary,{getSurfaceDriverId:()=>this.driverId})}attach(e,t){this.disposed||(this.onDeliveryError=t.onDeliveryError,this.outbound=Y(t=>e(JSON.stringify(t)),(e,t)=>this.reportDeliveryError(e,t),t.pendingBytes),this.holding=t.awaitResume===!0)}detach(){this.outbound=void 0,this.onDeliveryError=void 0}setDriverId(e){this.driverId=e}onClientMessage(e){if(this.disposed)return;let t=ie(e,this.emitBoundary);if(t){if(t.type===`resume`){this.replay(t.lastSeq);return}if(t.type===`ack`){this.buffer.ackThrough(t.seq);return}U(this.session,this.emitBoundary,t,this.driverId,this.reporters,this.surface,void 0,void 0,this.commandSurfaceLocality,{signal:this.lifetime.signal,...this.maxFrameBytes===void 0?{}:{maxFrameBytes:this.maxFrameBytes}})}}dispose(){this.disposed||(this.disposed=!0,this.lifetime.abort(),this.detach(),this.unsubscribe())}get lastSeq(){return this.buffer.lastSeq}emit(e){if(this.disposed)return;let t=this.buffer.append(e);this.holding||this.deliverFrame({...e,seq:t})}replay(e){let t=this.buffer.tailAfter(e);if(t.kind===`overrun`){this.deliverFrame({type:`resume_gap`}),this.holding=!1;return}for(let e of t.frames)this.deliverFrame({...e.message,seq:e.seq});this.holding=!1}deliverFrame(e){this.outbound?.(e)}reportDeliveryError(e,t){let n=this.onDeliveryError;this.detach();try{n?.(e,t)}catch{}}};function Xe(){return{origins:new Map}}function Ze(e,t,n){let r=t.origin.sessionId,i=e.origins.get(r);i===void 0&&(i={highestSequence:0,delivered:new Set,issued:new Map},e.origins.set(r,i));let a=i.issued.get(t.id);if(a!==void 0)return{ack:{...a,state:`duplicate`},deliver:!1};if(n!==void 0){let e={id:t.id,sequence:t.sequence,state:n.state,reason:n.reason};return i.issued.set(t.id,e),{ack:e,deliver:!1}}if(i.delivered.has(t.sequence)){let e={id:t.id,sequence:t.sequence,state:`refused`,reason:`sequence ${t.sequence} was already delivered by this origin under a different message id. A retry must repeat its id; a new id on a delivered sequence cannot be ordered against what the receiver already has.`};return i.issued.set(t.id,e),{ack:e,deliver:!1}}let o=i.highestSequence+1,s=t.sequence>o?o:void 0,c={id:t.id,sequence:t.sequence,state:`delivered`};return i.issued.set(t.id,c),i.delivered.add(t.sequence),i.highestSequence=Math.max(i.highestSequence,t.sequence),s===void 0?{ack:c,deliver:!0}:{ack:c,deliver:!0,gapBefore:s}}function Qe(e,t,n){let r=e.origins.get(t),i=r?.issued.get(n);if(r===void 0||i===void 0||i.state!==`delivered`)return;let a={...i,state:`acknowledged`};return r.issued.set(n,a),a}function $e(e,t){e.origins.delete(t)}const et=[`command`,`filePath`,`file_path`,`path`,`url`,`pattern`,`query`];function tt(e){for(let t of et){let n=e[t];if(typeof n==`string`&&n!==``)return n}}const $=16*1024,nt=32768;function rt(e){let t=``;for(let n=0;n<e.length;n+=nt)t+=String.fromCharCode(...e.subarray(n,n+nt));return btoa(t)}function it(e){let t;try{t=atob(e)}catch{return}let n=new Uint8Array(t.length);for(let e=0;e<t.length;e+=1)n[e]=t.charCodeAt(e);return n}function at(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}function ot(e,t,n=$){if(!Number.isInteger(n)||n<1)throw Error(`handoff chunking: maxChunkBytes must be a positive integer, got ${n}. A zero or negative budget produces an infinite sequence of empty chunks, which presents as a hang rather than as the configuration error it is.`);let r=new TextEncoder().encode(t),i=Math.max(1,Math.ceil(r.byteLength/n)),a=[];for(let t=0;t<i;t+=1){let o=r.subarray(t*n,(t+1)*n);a.push({handoffId:e,index:t,total:i,data:rt(o)})}return a}var st=class{handoffId;chunks=new Map;declaredTotal;constructor(e){this.handoffId=e}get received(){return this.chunks.size}get expected(){return this.declaredTotal}accept(e){let t=e=>({outcome:`refused`,rejection:e,received:this.chunks.size,...this.declaredTotal===void 0?{}:{expected:this.declaredTotal}});if(e.handoffId!==this.handoffId)return t(`wrong-handoff`);if(!Number.isInteger(e.total)||e.total<1||this.declaredTotal!==void 0&&e.total!==this.declaredTotal)return t(`inconsistent-total`);if(!Number.isInteger(e.index)||e.index<0||e.index>=e.total)return t(`out-of-range`);let n=it(e.data);if(n===void 0||rt(n)!==e.data)return t(`undecodable`);this.declaredTotal=e.total;let r=this.chunks.get(e.index);return r===void 0?(this.chunks.set(e.index,n),this.chunks.size<e.total?{outcome:`accepted`,received:this.chunks.size,expected:e.total}:{outcome:`complete`,serialized:this.reassemble(e.total),received:this.chunks.size,expected:e.total}):at(r,n)?{outcome:`duplicate`,received:this.chunks.size,expected:e.total}:t(`inconsistent-total`)}missing(){if(this.declaredTotal===void 0)return[];let e=[];for(let t=0;t<this.declaredTotal;t+=1)this.chunks.has(t)||e.push(t);return e}discard(){this.chunks.clear(),this.declaredTotal=void 0}reassemble(e){let t=[];for(let n=0;n<e;n+=1){let r=this.chunks.get(n);if(r===void 0)throw Error(`handoff chunking: chunk ${n} of ${e} is missing at reassembly.`);t.push(r)}let n=new Uint8Array(t.reduce((e,t)=>e+t.length,0)),r=0;for(let e of t)n.set(e,r),r+=e.length;return new TextDecoder(`utf-8`,{ignoreBOM:!0}).decode(n)}};function ct(e,t=$){return Math.max(1,Math.ceil(e.byteLength/t))}export{Re as ATTACHED_SURFACE_MAX_PENDING_BYTES,Q as CHANNEL_FRAME_MAGIC,Be as CHANNEL_FRAME_VERSION,$ as DEFAULT_MAX_CHUNK_BYTES,G as DEFAULT_MAX_PENDING_BYTES,q as DEFAULT_MAX_PENDING_MS,be as HISTORY_PAGE_MAX_BYTES,st as HandoffChunkAssembler,n as MAX_INBOUND_FRAME_BYTES,ve as PROTOCOL_SESSION_EVENT_CLASSIFICATION,X as ResumeBuffer,Ye as SessionResumeBridge,Qe as acknowledgePeerMessage,Ze as admitPeerMessage,ct as chunkCountFor,ot as chunkHandoffPayload,Y as createOutboundDelivery,Xe as createPeerMessageLedger,J as createPendingStallClock,Ae as createSessionMessageHandler,Je as decodeChannelFrame,r as decodeClientMessage,t as decodeFrame,e as decodeServerMessage,We as encodeBinaryFrame,Ge as encodeChannelEventFrame,$e as forgetPeerOrigin,ke as handleSettingsMessage,Ke as isChannelFrame,i as isObserverMessageType,K as isOverPendingBudget,Oe as isSettingsMessage,tt as permissionSummaryArgument};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["failureMessage","failureMessage"],"sources":["../../src/app-tool-messages.ts","../../src/background-messages.ts","../../src/loop-control-messages.ts","../../src/usage-messages.ts","../../src/message-parser.ts","../../src/project-read-messages.ts","../../src/session-directory-messages.ts","../../src/open-prompts.ts","../../src/session-error-classification.ts","../../src/session-events.ts","../../src/session-query-messages.ts","../../src/settings-messages.ts","../../src/session-message-handler.ts","../../src/outbound-delivery.ts","../../src/resume-buffer.ts","../../src/channel-frames.ts","../../src/session-resume-bridge.ts","../../src/peer-message-ledger.ts","../../src/permission-summary.ts","../../src/handoff-chunking.ts"],"sourcesContent":["/**\n * A driving surface that hosts an MCP app view acts for it here: it invokes one runtime tool\n * through the session's own permission gate, and it reads a finished call's view on demand.\n *\n * Both are drive-only (neither is in `OBSERVER_MESSAGES`): an observer neither runs a tool nor\n * receives a view's document. A message that decoded but cannot be served answers `protocol_error`\n * echoing `requestId`, so the view that asked can tell which call it was.\n */\nimport { MAX_INBOUND_FRAME_BYTES } from './message-decoders.js';\n\nimport type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { TClientMessage, TServerMessage } from './wire-messages.js';\nimport type {\n ISessionRuntimeTools,\n ISessionToolAppViews,\n TDriverId,\n} from '@robota-sdk/agent-interface-session';\n\ntype TCallAppToolMessage = Extract<TClientMessage, { type: 'call-app-tool' }>;\ntype TReadAppViewMessage = Extract<TClientMessage, { type: 'read-app-view' }>;\n\n/**\n * Room a carrier that stamps frames after they are produced (the resume bridge adds `seq`) needs\n * beneath the inbound limit, so a frame sent within the limit is still received within it.\n */\nconst FRAME_STAMP_MARGIN_BYTES = 1024;\n\n/** Where a frame answering an app message is measured, and what ends the work behind it. */\nexport interface IAppMessageOptions {\n /** The most bytes one answering frame may take; defaults to the inbound limit less a margin. */\n readonly maxFrameBytes?: number;\n /** Ends the connection's work: aborting it cancels a call still waiting on its prompt. */\n readonly signal?: AbortSignal;\n}\n\nexport function isAppToolMessage(msg: TClientMessage): msg is TCallAppToolMessage {\n return msg.type === 'call-app-tool';\n}\n\nexport function isAppViewMessage(msg: TClientMessage): msg is TReadAppViewMessage {\n return msg.type === 'read-app-view';\n}\n\nfunction frameLimit(options: IAppMessageOptions | undefined): number {\n return options?.maxFrameBytes ?? MAX_INBOUND_FRAME_BYTES - FRAME_STAMP_MARGIN_BYTES;\n}\n\n/** Checked before `deliver`, so a frame the channel cannot carry never enters a buffer. */\nfunction fitsFrame(frame: TServerMessage, limit: number): boolean {\n return new TextEncoder().encode(JSON.stringify(frame)).byteLength <= limit;\n}\n\n/**\n * Runs the tool as an interactive call acting for this connection's driver, so a permission prompt\n * it raises is shown to the surfaces that can answer and is attributed to that driver. The call is\n * limited to the tools the named view's own source declared: the surface says which view it acts\n * for, and the session, not the surface, says what that view may reach.\n */\nexport function handleAppToolMessage(\n session: ISessionRuntimeTools & ISessionToolAppViews,\n deliver: TOutboundDeliver,\n msg: TCallAppToolMessage,\n driverId?: TDriverId,\n options?: IAppMessageOptions,\n): void {\n const { requestId } = msg;\n const view = session.readToolAppView(msg.viewId);\n if (view === undefined) {\n deliver({ type: 'protocol_error', message: 'This app view is no longer available.', requestId });\n return;\n }\n if (!Object.values(view.callableTools).includes(msg.name)) {\n deliver({\n type: 'protocol_error',\n message: 'This tool is not available to this app view.',\n requestId,\n });\n return;\n }\n const { signal } = options ?? {};\n const limit = frameLimit(options);\n session\n .invokeRuntimeTool(msg.name, msg.arguments, {\n permissionInteraction: 'interactive',\n ...(driverId ? { driverId } : {}),\n ...(signal ? { signal } : {}),\n })\n .then(\n (result) => {\n // The connection that asked is gone: there is nobody to answer.\n if (signal?.aborted) return;\n const frame: TServerMessage = {\n type: 'app_tool_result',\n requestId,\n success: result.success,\n ...(result.result !== undefined ? { data: result.result } : {}),\n ...(result.parts !== undefined ? { parts: result.parts } : {}),\n ...(result.error !== undefined ? { error: result.error } : {}),\n };\n if (fitsFrame(frame, limit)) {\n deliver(frame);\n } else {\n deliver({\n type: 'protocol_error',\n message: 'This app tool result is too large to send.',\n requestId,\n });\n }\n },\n (error: unknown) => {\n if (signal?.aborted) return;\n deliver({\n type: 'protocol_error',\n message: error instanceof Error ? error.message : String(error),\n requestId,\n });\n },\n );\n}\n\n/** Answers with the whole document, or says why not: gone, or too large for a frame. */\nexport function handleAppViewMessage(\n session: ISessionToolAppViews,\n deliver: TOutboundDeliver,\n msg: TReadAppViewMessage,\n options?: IAppMessageOptions,\n): void {\n const { requestId } = msg;\n const view = session.readToolAppView(msg.viewId);\n if (view === undefined) {\n deliver({ type: 'protocol_error', message: 'This app view is no longer available.', requestId });\n return;\n }\n const frame: TServerMessage = { type: 'app_view', requestId, view };\n if (!fitsFrame(frame, frameLimit(options))) {\n deliver({ type: 'protocol_error', message: 'This app view is too large to send.', requestId });\n return;\n }\n deliver(frame);\n}\n","import type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { IProtocolSession } from './protocol-session.js';\nimport type { TBackgroundControlAction, TClientMessage } from './wire-messages.js';\n\nexport function handleBackgroundQueryMessage(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: Extract<\n TClientMessage,\n | { type: 'get-background-tasks' | 'get-background-task' | 'read-background-task-log' }\n | {\n type:\n 'get-background-job-groups' | 'get-background-job-group' | 'wait-background-job-group';\n }\n >,\n): void {\n if (msg.type === 'get-background-tasks') {\n deliver({ type: 'background_tasks', tasks: session.listBackgroundTasks(msg.filter) });\n return;\n }\n if (msg.type === 'get-background-task') {\n sendBackgroundTaskSnapshot(session, deliver, msg);\n return;\n }\n if (msg.type === 'get-background-job-groups') {\n deliver({ type: 'background_job_groups', groups: session.listBackgroundJobGroups() });\n return;\n }\n if (msg.type === 'get-background-job-group') {\n sendBackgroundJobGroupSnapshot(session, deliver, msg);\n return;\n }\n if (msg.type === 'wait-background-job-group') {\n sendBackgroundJobGroupWaitResult(session, deliver, msg);\n return;\n }\n sendBackgroundTaskLogPage(session, deliver, msg);\n}\n\nexport function handleBackgroundControlMessage(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: Extract<\n TClientMessage,\n { type: 'cancel-background-task' | 'close-background-task' | 'send-background-task' }\n >,\n): void {\n if (!msg.taskId) {\n deliver({ type: 'protocol_error', message: 'taskId is required' });\n return;\n }\n if (msg.type === 'cancel-background-task') {\n sendBackgroundTaskControlResult(\n deliver,\n 'cancel',\n msg.taskId,\n session.cancelBackgroundTask(msg.taskId, msg.reason),\n );\n return;\n }\n if (msg.type === 'close-background-task') {\n sendBackgroundTaskControlResult(\n deliver,\n 'close',\n msg.taskId,\n session.closeBackgroundTask(msg.taskId),\n );\n return;\n }\n sendBackgroundTaskInput(session, deliver, msg);\n}\n\nfunction sendBackgroundTaskSnapshot(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: Extract<TClientMessage, { type: 'get-background-task' }>,\n): void {\n if (!msg.taskId) {\n deliver({ type: 'protocol_error', message: 'taskId is required' });\n return;\n }\n deliver({\n type: 'background_task',\n taskId: msg.taskId,\n task: session.getBackgroundTask(msg.taskId) ?? null,\n });\n}\n\nfunction sendBackgroundTaskLogPage(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: Extract<TClientMessage, { type: 'read-background-task-log' }>,\n): void {\n if (!msg.taskId) {\n deliver({ type: 'protocol_error', message: 'taskId is required' });\n return;\n }\n session.readBackgroundTaskLog(msg.taskId, msg.cursor).then(\n (page) => deliver({ type: 'background_task_log', taskId: msg.taskId, page }),\n (error: Error) => deliver({ type: 'protocol_error', message: error.message }),\n );\n}\n\nfunction sendBackgroundJobGroupSnapshot(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: Extract<TClientMessage, { type: 'get-background-job-group' }>,\n): void {\n if (!msg.groupId) {\n deliver({ type: 'protocol_error', message: 'groupId is required' });\n return;\n }\n deliver({\n type: 'background_job_group',\n groupId: msg.groupId,\n group: session.getBackgroundJobGroup(msg.groupId) ?? null,\n });\n}\n\nfunction sendBackgroundJobGroupWaitResult(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: Extract<TClientMessage, { type: 'wait-background-job-group' }>,\n): void {\n if (!msg.groupId) {\n deliver({ type: 'protocol_error', message: 'groupId is required' });\n return;\n }\n session.waitBackgroundJobGroup(msg.groupId).then(\n (group) => deliver({ type: 'background_job_group', groupId: msg.groupId, group }),\n (error: Error) => deliver({ type: 'protocol_error', message: error.message }),\n );\n}\n\nfunction sendBackgroundTaskInput(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: Extract<TClientMessage, { type: 'send-background-task' }>,\n): void {\n if (!msg.input) {\n deliver({ type: 'protocol_error', message: 'input is required' });\n return;\n }\n sendBackgroundTaskControlResult(\n deliver,\n 'send',\n msg.taskId,\n session.sendBackgroundTask(msg.taskId, msg.input),\n );\n}\n\nfunction sendBackgroundTaskControlResult(\n deliver: TOutboundDeliver,\n action: TBackgroundControlAction,\n taskId: string,\n operation: Promise<void>,\n): void {\n operation.then(\n () => deliver({ type: 'background_task_control_result', action, taskId, success: true }),\n (error: Error) =>\n deliver({\n type: 'background_task_control_result',\n action,\n taskId,\n success: false,\n message: error.message,\n }),\n );\n}\n","import type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { IProtocolSession } from './protocol-session.js';\nimport type { TClientMessage } from './wire-messages.js';\n\ntype TLoopControlMessage = Extract<TClientMessage, { type: 'stop-waiting-loop' }>;\n\nexport function isLoopControlMessage(msg: TClientMessage): msg is TLoopControlMessage {\n return msg.type === 'stop-waiting-loop';\n}\n\n/**\n * Stop the self-paced loop that is waiting for its next wake. The session decides which loop, if\n * any; the reply carries its outcome, and a session that throws is reported as a failed stop.\n */\nexport function handleLoopControlMessage(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: TLoopControlMessage,\n): void {\n const { requestId } = msg;\n session.stopWaitingSelfPacedLoop().then(\n (outcome) => deliver({ type: 'waiting_loop_stop', requestId, outcome }),\n (error: unknown) =>\n deliver({\n type: 'waiting_loop_stop',\n requestId,\n outcome: { kind: 'failed', message: errorMessage(error) },\n }),\n );\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n","import { MAX_INBOUND_FRAME_BYTES } from './message-decoders.js';\n\nimport type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { IProtocolSession } from './protocol-session.js';\nimport type { TClientMessage } from './wire-messages.js';\nimport type {\n IPersonalUsageReport,\n IPersonalUsageRequest,\n IUsageBySourceReport,\n} from '@robota-sdk/agent-interface-analytics';\n\ntype TJsonValue =\n string | number | boolean | null | readonly TJsonValue[] | { readonly [key: string]: TJsonValue };\n\nexport interface IUsageQueryReporters {\n readonly personalUsageReporter:\n | ((request: IPersonalUsageRequest) => IPersonalUsageReport | Promise<IPersonalUsageReport>)\n | undefined;\n readonly usageReporter:\n | ((session: IProtocolSession) => IUsageBySourceReport | Promise<IUsageBySourceReport>)\n | undefined;\n readonly storedSessionUsageReporter:\n ((sessionId: string) => IUsageBySourceReport | Promise<IUsageBySourceReport>) | undefined;\n}\n\nexport type TMalformedUsageRequest =\n | { type: 'personal'; requestId: string }\n | { type: 'stored-session'; requestId: string; sessionId: string };\n\nfunction isJsonObject(value: TJsonValue): value is { readonly [key: string]: TJsonValue } {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction optionalNonEmptyString(value: TJsonValue | undefined): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nexport function malformedUsageRequest(data: string): TMalformedUsageRequest | undefined {\n if (new TextEncoder().encode(data).byteLength > MAX_INBOUND_FRAME_BYTES) return undefined;\n try {\n const value = JSON.parse(data) as TJsonValue;\n if (!isJsonObject(value)) return undefined;\n const requestId = optionalNonEmptyString(value['requestId']);\n if (!requestId) return undefined;\n if (value['type'] === 'get-personal-usage-report') return { type: 'personal', requestId };\n const sessionId = optionalNonEmptyString(value['sessionId']);\n return value['type'] === 'get-stored-session-usage-report' && sessionId\n ? { type: 'stored-session', requestId, sessionId }\n : undefined;\n } catch {\n // allow-fallback: malformed JSON has no trustworthy request id to correlate, so the caller emits\n // the protocol's uncorrelated decode error without echoing attacker-controlled content.\n return undefined;\n }\n}\n\nexport function handleUsageQueryMessage(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: TClientMessage,\n reporters: IUsageQueryReporters,\n): boolean {\n if (msg.type === 'get-personal-usage-report') {\n handlePersonalUsageQuery(deliver, msg, reporters.personalUsageReporter);\n return true;\n }\n if (msg.type === 'get-stored-session-usage-report') {\n handleStoredSessionUsageQuery(deliver, msg, reporters.storedSessionUsageReporter);\n return true;\n }\n if (msg.type === 'get-usage-report') {\n handleCurrentUsageQuery(session, deliver, reporters.usageReporter);\n return true;\n }\n return false;\n}\n\nfunction handleCurrentUsageQuery(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n reporter: IUsageQueryReporters['usageReporter'],\n): void {\n if (!reporter) {\n deliver({\n type: 'protocol_error',\n message: 'Per-session usage reporting is not available on this host.',\n });\n return;\n }\n try {\n void Promise.resolve(reporter(session)).then(\n (report) => deliver({ type: 'usage_report', report }),\n (error) => deliver({ type: 'protocol_error', message: failureMessage(error) }),\n );\n } catch (error) {\n deliver({\n type: 'protocol_error',\n message: failureMessage(error instanceof Error ? error : String(error)),\n });\n }\n}\n\nfunction handleStoredSessionUsageQuery(\n deliver: TOutboundDeliver,\n msg: Extract<TClientMessage, { type: 'get-stored-session-usage-report' }>,\n reporter: IUsageQueryReporters['storedSessionUsageReporter'],\n): void {\n if (!reporter) {\n deliverStoredSessionUsageError(\n deliver,\n msg,\n 'not_available',\n 'Stored-session usage reporting is not available on this host.',\n );\n return;\n }\n try {\n void Promise.resolve(reporter(msg.sessionId)).then(\n (report) =>\n deliver({\n type: 'stored_session_usage_report',\n requestId: msg.requestId,\n sessionId: msg.sessionId,\n report,\n }),\n (error) =>\n deliverStoredSessionUsageError(deliver, msg, 'report_failed', failureMessage(error)),\n );\n } catch (error) {\n deliverStoredSessionUsageError(\n deliver,\n msg,\n 'report_failed',\n failureMessage(error instanceof Error ? error : String(error)),\n );\n }\n}\n\nfunction deliverStoredSessionUsageError(\n deliver: TOutboundDeliver,\n msg: Extract<TClientMessage, { type: 'get-stored-session-usage-report' }>,\n code: 'not_available' | 'report_failed',\n message: string,\n): void {\n deliver({\n type: 'stored_session_usage_report_error',\n requestId: msg.requestId,\n sessionId: msg.sessionId,\n code,\n message,\n });\n}\n\nfunction handlePersonalUsageQuery(\n deliver: TOutboundDeliver,\n msg: Extract<TClientMessage, { type: 'get-personal-usage-report' }>,\n reporter: IUsageQueryReporters['personalUsageReporter'],\n): void {\n if (!reporter) {\n deliverPersonalUsageError(\n deliver,\n msg.requestId,\n 'not_available',\n 'Personal usage reporting is not available on this host.',\n );\n return;\n }\n try {\n void Promise.resolve(reporter({ period: msg.period, timezone: msg.timezone })).then(\n (report) => deliver({ type: 'personal_usage_report', requestId: msg.requestId, report }),\n (error) =>\n deliverPersonalUsageError(deliver, msg.requestId, 'report_failed', failureMessage(error)),\n );\n } catch (error) {\n deliverPersonalUsageError(\n deliver,\n msg.requestId,\n 'report_failed',\n failureMessage(error instanceof Error ? error : String(error)),\n );\n }\n}\n\nfunction deliverPersonalUsageError(\n deliver: TOutboundDeliver,\n requestId: string,\n code: 'not_available' | 'report_failed',\n message: string,\n): void {\n deliver({ type: 'personal_usage_report_error', requestId, code, message });\n}\n\nfunction failureMessage(error: Error | TJsonValue | undefined): string {\n return error instanceof Error ? error.message : String(error);\n}\n","import { decodeClientMessage, decodeFrame } from './message-decoders.js';\nimport { malformedUsageRequest } from './usage-messages.js';\n\nimport type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { TClientMessage } from './wire-messages.js';\n\n/** Decode a client frame and preserve validated usage-request correlation on malformed payloads. */\nexport function parseClientMessage(data: string, deliver: TOutboundDeliver): TClientMessage | null {\n const decoded = decodeFrame(data, decodeClientMessage);\n if (decoded.ok) return decoded.message;\n const malformedUsage = malformedUsageRequest(data);\n if (malformedUsage?.type === 'personal') {\n deliver({\n type: 'personal_usage_report_error',\n requestId: malformedUsage.requestId,\n code: 'report_failed',\n message: 'Invalid personal usage report request.',\n });\n return null;\n }\n if (malformedUsage?.type === 'stored-session') {\n deliver({\n type: 'stored_session_usage_report_error',\n requestId: malformedUsage.requestId,\n sessionId: malformedUsage.sessionId,\n code: 'report_failed',\n message: 'Invalid stored-session usage report request.',\n });\n return null;\n }\n deliver({ type: 'protocol_error', message: decoded.reason });\n return null;\n}\n","/**\n * #3282 §4c — the Project panel's reads (git status, one file's diff, project memory).\n *\n * `TProjectReadCapableSession` is `IProtocolSession & Partial<ISessionProjectRead>` (see\n * `protocol-session.ts`'s doc comment): the three methods are runtime-probed, never assumed, so\n * adding this capability changes nothing for a session or test double that does not have it — a\n * request against one answers `protocol_error` (host-neutral, not a made-up \"kind\"), the same way an\n * unknown message type already does.\n */\nimport type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { TProjectReadCapableSession } from './protocol-session.js';\nimport type { TClientMessage } from './wire-messages.js';\n\ntype TProjectReadMessage = Extract<\n TClientMessage,\n { type: 'project-status' | 'project-diff' | 'project-memory' }\n>;\n\nexport function isProjectReadMessage(msg: TClientMessage): msg is TProjectReadMessage {\n return msg.type === 'project-status' || msg.type === 'project-diff' || msg.type === 'project-memory';\n}\n\nfunction notSupported(deliver: TOutboundDeliver, requestId: string): void {\n deliver({\n type: 'protocol_error',\n message: 'This host does not support the Project panel.',\n requestId,\n });\n}\n\nexport function handleProjectReadMessage(\n session: TProjectReadCapableSession,\n deliver: TOutboundDeliver,\n msg: TProjectReadMessage,\n): void {\n const { requestId } = msg;\n if (msg.type === 'project-status') {\n if (!session.readProjectStatus) return notSupported(deliver, requestId);\n session.readProjectStatus().then(\n (result) => deliver({ type: 'project_status', requestId, result }),\n (error: unknown) =>\n deliver({\n type: 'project_status',\n requestId,\n result: { kind: 'failed', message: error instanceof Error ? error.message : String(error) },\n }),\n );\n } else if (msg.type === 'project-diff') {\n if (!session.readProjectDiff) return notSupported(deliver, requestId);\n session.readProjectDiff(msg.path).then(\n (result) => deliver({ type: 'project_diff', requestId, result }),\n (error: unknown) =>\n deliver({\n type: 'project_diff',\n requestId,\n result: { kind: 'failed', message: error instanceof Error ? error.message : String(error) },\n }),\n );\n } else {\n if (!session.readProjectMemory) return notSupported(deliver, requestId);\n session.readProjectMemory().then(\n (result) => deliver({ type: 'project_memory', requestId, result }),\n (error: unknown) =>\n deliver({\n type: 'project_memory',\n requestId,\n result: {\n kind: 'unavailable',\n message: error instanceof Error ? error.message : String(error),\n },\n }),\n );\n }\n}\n","import { isSessionChangeRefusal, isSessionDeleteRefusal } from '@robota-sdk/agent-interface-session';\n\nimport type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { TClientMessage } from './wire-messages.js';\nimport type { ISessionDirectory } from '@robota-sdk/agent-interface-session';\n\ntype TSessionDirectoryMessage = Extract<\n TClientMessage,\n { type: 'list-sessions' | 'new-session' | 'switch-session' }\n>;\n\nexport function isSessionDirectoryMessage(msg: TClientMessage): msg is TSessionDirectoryMessage {\n return (\n msg.type === 'list-sessions' || msg.type === 'new-session' || msg.type === 'switch-session'\n );\n}\n\ntype TSessionRenameMessage = Extract<TClientMessage, { type: 'rename-session' }>;\ntype TSessionDeleteMessage = Extract<TClientMessage, { type: 'delete-session' }>;\n\nexport function isSessionRenameMessage(msg: TClientMessage): msg is TSessionRenameMessage {\n return msg.type === 'rename-session';\n}\n\nexport function isSessionDeleteMessage(msg: TClientMessage): msg is TSessionDeleteMessage {\n return msg.type === 'delete-session';\n}\n\n/**\n * #3189: the host's sessions. Listing answers on its own correlated reply. A new session or a switch\n * answers nothing on success: `session_switched` from the connection's own session is the signal. A\n * refusal answers `session_change_failed`, carrying the host's code (or `failed` for an error that is\n * not a declared refusal) and the request's `requestId`.\n */\nexport function handleSessionDirectoryMessage(\n deliver: TOutboundDeliver,\n msg: TSessionDirectoryMessage,\n directory: ISessionDirectory | undefined,\n): void {\n if (msg.type === 'list-sessions') {\n listSessions(deliver, msg.requestId, directory);\n return;\n }\n const request = msg.requestId !== undefined ? { requestId: msg.requestId } : {};\n if (!directory) {\n deliver({\n type: 'session_change_failed',\n code: 'not_available',\n message: 'Sessions cannot be switched on this host.',\n ...request,\n });\n return;\n }\n const change = (): Promise<void> =>\n msg.type === 'new-session' ? directory.newSession() : directory.switchSession(msg.sessionId);\n void Promise.resolve()\n .then(change)\n .catch((error: Error | string) =>\n deliver({\n type: 'session_change_failed',\n code: isSessionChangeRefusal(error) ? error.code : 'failed',\n message: failureMessage(error),\n ...request,\n }),\n );\n}\n\nfunction listSessions(\n deliver: TOutboundDeliver,\n requestId: string,\n directory: ISessionDirectory | undefined,\n): void {\n if (!directory) {\n deliver({\n type: 'sessions_error',\n requestId,\n code: 'not_available',\n message: 'Session listing is not available on this host.',\n });\n return;\n }\n try {\n deliver({ type: 'sessions', requestId, listing: directory.listSessions() });\n } catch (error) {\n deliver({\n type: 'sessions_error',\n requestId,\n code: 'list_failed',\n message: failureMessage(error instanceof Error ? error : String(error)),\n });\n }\n}\n\nfunction failureMessage(error: Error | string): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * #3289 §1: rename a session from the list — current or not. Success replies with the new name so\n * the client can update it optimistically; the client also re-lists to pick up everything else a\n * rename can imply (nothing today, but the reply shape matches `delete-session`'s on purpose).\n */\nexport function handleSessionRenameMessage(\n deliver: TOutboundDeliver,\n msg: TSessionRenameMessage,\n directory: ISessionDirectory | undefined,\n): void {\n if (!directory) {\n deliver({\n type: 'session_rename_failed',\n requestId: msg.requestId,\n message: 'Sessions cannot be renamed on this host.',\n });\n return;\n }\n directory.renameSession(msg.sessionId, msg.name).then(\n () =>\n deliver({\n type: 'session_renamed_in_list',\n requestId: msg.requestId,\n sessionId: msg.sessionId,\n name: msg.name,\n }),\n (error: Error | string) =>\n deliver({\n type: 'session_rename_failed',\n requestId: msg.requestId,\n message: failureMessage(error),\n }),\n );\n}\n\n/**\n * #3289 §1: remove a stored session's record. Refused when it is live on another client or running a\n * turn; deleting the current session switches this binding away first (the directory's own job), so\n * by the time this answers, the caller's own session is never the one just removed.\n */\nexport function handleSessionDeleteMessage(\n deliver: TOutboundDeliver,\n msg: TSessionDeleteMessage,\n directory: ISessionDirectory | undefined,\n): void {\n if (!directory) {\n deliver({\n type: 'session_delete_failed',\n requestId: msg.requestId,\n code: 'not_available',\n message: 'Sessions cannot be deleted on this host.',\n });\n return;\n }\n directory.deleteSession(msg.sessionId).then(\n () => deliver({ type: 'session_deleted', requestId: msg.requestId, sessionId: msg.sessionId }),\n (error: Error | string) =>\n deliver({\n type: 'session_delete_failed',\n requestId: msg.requestId,\n code: isSessionDeleteRefusal(error) ? error.code : 'failed',\n message: failureMessage(error),\n }),\n );\n}\n","/**\n * #3189: the permission and ask prompts a session has open, as the frames that asked them.\n *\n * The session keeps only the promise each prompt waits on, and emits the prompt once, when it is\n * asked. A client that attaches later would never see a prompt already open. This keeps what the\n * transport forwarded, per session, for a `get-prompts` reply.\n *\n * It is held by the connections that receive prompts and learns everything through their own\n * subscriptions, so it never listens to the session itself. That matters: a session asks only while\n * someone listens for prompts, and a listener of its own here would keep an unattended session's\n * prompt open instead of failing it closed. When the last such connection goes, the session settles\n * every open prompt, so nothing is kept.\n */\n\nimport type { IProtocolSession } from './protocol-session.js';\nimport type { TServerMessage } from './wire-messages.js';\n\nexport type TOpenPromptFrame = Extract<\n TServerMessage,\n { type: 'permission_request' | 'ask_request' }\n>;\n\ninterface IOpenPrompts {\n readonly frames: Map<string, TOpenPromptFrame>;\n holders: number;\n}\n\nconst openPromptsBySession = new WeakMap<IProtocolSession, IOpenPrompts>();\n\n/** One connection's hold on its session's open prompts; every holder reports what it forwards. */\nexport interface IOpenPromptsHold {\n /** A prompt was asked. */\n record(frame: TOpenPromptFrame): void;\n /** A prompt was settled, by an answer or by the session. */\n forget(id: string): void;\n /** The host made another session current; the previous session's prompts are settled. */\n clear(): void;\n /** This connection no longer receives prompts. */\n release(): void;\n}\n\n/** Called by a connection that receives prompts, before it subscribes to them. */\nexport function holdOpenPrompts(session: IProtocolSession): IOpenPromptsHold {\n let open = openPromptsBySession.get(session);\n if (open === undefined) {\n open = { frames: new Map(), holders: 0 };\n openPromptsBySession.set(session, open);\n }\n const held = open;\n held.holders += 1;\n let released = false;\n return {\n record: (frame) => {\n if (!released) held.frames.set(frame.event.id, frame);\n },\n forget: (id) => {\n held.frames.delete(id);\n },\n clear: () => {\n held.frames.clear();\n },\n release: () => {\n if (released) return;\n released = true;\n held.holders -= 1;\n if (held.holders === 0 && openPromptsBySession.get(session) === held) {\n openPromptsBySession.delete(session);\n }\n },\n };\n}\n\n/** The prompts open now, oldest first; none when no connection that receives prompts is attached. */\nexport function listOpenPrompts(session: IProtocolSession): readonly TOpenPromptFrame[] {\n return [...(openPromptsBySession.get(session)?.frames.values() ?? [])];\n}\n","/**\n * What the `error` wire frame should say about a session failure, beyond its raw `message` (#3289\n * §3). A renderer maps `code` to one plain sentence with the next step, keeping `message` behind a\n * \"Details\" disclosure — see `agent-ui-web`'s `SessionNotices`.\n *\n * Walks wrapped layers (`originalError`/`cause`, a few deep) because a higher-level catch can wrap the\n * provider's own typed error in another one (e.g. a \"Conversation failed\" `ProviderError`) before it\n * reaches here — the same reason `agent-core`'s `classifyProviderFailure` walks layers rather than\n * looking at the outermost error alone.\n */\n\nimport {\n AuthenticationError,\n ModelNotAvailableError,\n NetworkError,\n ProviderError,\n RateLimitError,\n} from '@robota-sdk/agent-core';\n\n/** Kept in sync with `wire-messages.ts`'s `error` variant — see that type for what each means. */\nexport type TSessionErrorCode =\n 'auth' | 'rate_limit' | 'model_unavailable' | 'network' | 'provider';\n\nexport interface ISessionErrorClassification {\n code?: TSessionErrorCode;\n provider?: string;\n retryAfterSeconds?: number;\n /** For `code: 'model_unavailable'`, the model the request tried, when the failure named one. */\n model?: string;\n}\n\nconst MAX_WRAP_DEPTH = 8;\n\n/** The error and everything it wraps, outermost first, followed to a small depth and never twice. */\nfunction layersOf(error: unknown): unknown[] {\n const layers: unknown[] = [];\n const seen = new Set<unknown>();\n let frontier: unknown[] = [error];\n for (let depth = 0; depth <= MAX_WRAP_DEPTH && frontier.length > 0; depth++) {\n const next: unknown[] = [];\n for (const layer of frontier) {\n if (layer === undefined || layer === null || seen.has(layer)) continue;\n seen.add(layer);\n layers.push(layer);\n if (typeof layer === 'object') {\n const record = layer as Record<string, unknown>;\n next.push(record['originalError'], record['cause']);\n }\n }\n frontier = next;\n }\n return layers;\n}\n\n/**\n * Classify a session failure for the wire, or leave it uncoded when it names no provider at all —\n * an unrelated internal failure keeps showing its raw `message` exactly as before this classification\n * existed.\n */\nexport function classifySessionErrorForWire(error: unknown): ISessionErrorClassification {\n // A generic ProviderError never wins outright — a wrapper (e.g. \"Conversation failed\") can sit\n // above the real, more specific cause, so it is kept only as a fallback while deeper layers are\n // still searched for one of the specific types below.\n let fallback: ISessionErrorClassification | undefined;\n for (const layer of layersOf(error)) {\n if (layer instanceof AuthenticationError) {\n return { code: 'auth', ...(layer.provider !== undefined && { provider: layer.provider }) };\n }\n if (layer instanceof RateLimitError) {\n return {\n code: 'rate_limit',\n ...(layer.provider !== undefined && { provider: layer.provider }),\n ...(layer.retryAfter !== undefined && { retryAfterSeconds: layer.retryAfter }),\n };\n }\n if (layer instanceof ModelNotAvailableError) {\n return {\n code: 'model_unavailable',\n provider: layer.provider,\n ...(layer.model !== undefined && { model: layer.model }),\n };\n }\n if (layer instanceof NetworkError) {\n return { code: 'network', ...(layer.provider !== undefined && { provider: layer.provider }) };\n }\n if (fallback === undefined && layer instanceof ProviderError) {\n fallback = { code: 'provider', provider: layer.provider };\n }\n }\n return fallback ?? {};\n}\n","/**\n * Outbound session-event subscription for the WS/WebRTC transports (split from `session-message-handler.ts` —\n * the inbound message routing stays there; this module owns the session→`TServerMessage` fan-out).\n *\n * Used by `createSessionMessageHandler` (one subscription per channel) and by the persistent\n * `SessionResumeBridge` (REMOTE-013 E4 — a SINGLE subscription that outlives per-channel handlers).\n */\n\nimport { holdOpenPrompts } from './open-prompts.js';\nimport { classifySessionErrorForWire } from './session-error-classification.js';\n\nimport type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { IProtocolSession } from './protocol-session.js';\nimport type { TWireExecutionResult } from './wire-messages.js';\nimport type {\n IExecutionWorkspaceEvent,\n TBackgroundJobGroupEvent,\n TBackgroundTaskEvent,\n} from '@robota-sdk/agent-interface-execution';\nimport type {\n TDriverId,\n TInteractiveEventName,\n TTurnSource,\n} from '@robota-sdk/agent-interface-session';\nimport type {\n IAskRequestEvent,\n IBranchEvent,\n ICompactEvent,\n IContextFileRefreshedEvent,\n IExecutionResult,\n IPermissionRequestEvent,\n IPromptResolvedEvent,\n IPlanApprovalEvent,\n ISessionRenamedEvent,\n ISessionStatusSnapshot,\n ISessionSwitchedEvent,\n IToolState,\n IUiIntentEvent,\n} from '@robota-sdk/agent-interface-session';\n\nexport type TProtocolSessionEventClassification = 'forwarded' | 'requester-routed' | 'non-surface';\n\n/** Exhaustive protocol-owner policy over the shared session-event vocabulary. */\nexport const PROTOCOL_SESSION_EVENT_CLASSIFICATION = {\n text_delta: 'forwarded',\n tool_start: 'forwarded',\n tool_end: 'forwarded',\n thinking: 'forwarded',\n thinking_delta: 'non-surface',\n complete: 'forwarded',\n error: 'forwarded',\n context_update: 'forwarded',\n compact: 'forwarded',\n interrupted: 'forwarded',\n skill_activation: 'forwarded',\n background_task_event: 'forwarded',\n background_job_group_event: 'forwarded',\n execution_workspace_event: 'forwarded',\n user_message: 'forwarded',\n turn_source: 'forwarded',\n context_file_refreshed: 'forwarded',\n memory_event: 'forwarded',\n goal_event: 'non-surface',\n plan_event: 'forwarded',\n branch_event: 'forwarded',\n permission_request: 'forwarded',\n ask_request: 'forwarded',\n prompt_resolved: 'forwarded',\n ui_intent: 'requester-routed',\n session_renamed: 'forwarded',\n history_cleared: 'forwarded',\n session_switched: 'forwarded',\n status_changed: 'forwarded',\n} as const satisfies Record<TInteractiveEventName, TProtocolSessionEventClassification>;\n\n/**\n * Options for {@link subscribeSessionEvents}.\n *\n * ARCH-030: `onDeliveryError` is NOT here. Carrier-failure containment moved to the one\n * connection-scoped {@link TOutboundDeliver} boundary this function now receives, so the fan-out and\n * every reply share a single guard instead of the fan-out having its own.\n */\nexport interface ISubscribeSessionEventsOptions {\n /**\n * CMD-004 Stage D: lazily read the SERVER-ASSIGNED driver id of THIS surface — `ui_intent` is\n * requester-routed against it (lazy because the resume bridge binds the id only after pairing).\n */\n getSurfaceDriverId?: () => TDriverId | undefined;\n /**\n * `false` leaves `permission_request` and `ask_request` unsubscribed. The session parks a prompt\n * only while someone listens for it, so a surface that may not answer must not listen: otherwise an\n * unattended session would hold a prompt nobody can settle instead of failing it closed at once.\n * Defaults to `true`.\n */\n receivePrompts?: boolean;\n}\n\n/**\n * Subscribe the session's events and forward each as a `TServerMessage` through the connection's\n * outbound boundary; returns an unsubscribe. Exported (REMOTE-013 E4) so the persistent\n * `SessionResumeBridge` can own a SINGLE subscription that outlives per-channel handlers.\n *\n * ARCH-030: the second parameter is the boundary, not a raw sink — a carrier `send` cannot be passed\n * here, which is what keeps the fan-out and the reply paths on one guard.\n */\nexport function subscribeSessionEvents(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n options: ISubscribeSessionEventsOptions = {},\n): () => void {\n // ARCH-030: no local guard wrapper any more, and no event name passed alongside the message. The name\n // existed so the local guard could label a failure with it; the boundary labels with `message.type`,\n // the frame that could not be delivered. Keeping the argument would have been a parameter that\n // documents nothing and is read by nobody.\n // REMOTE-014 E5: stamp the ACTIVE turn's driver id onto TURN-AUTHORED events (co-drive authorship,\n // display-only), read at emit time. Only these events — background/goal/memory/execution-workspace events\n // are NOT authored by a driver turn and carry no `driverId`. `undefined` when idle or unattributed.\n const attr = (): { driverId?: TDriverId } => {\n // ARCH-012: a plain call, not `?.() ?? undefined`. The member is required, so `null` means\n // nobody is driving — it can no longer also mean \"this host cannot answer\".\n const driverId = session.getActiveDriverId();\n return driverId ? { driverId } : {};\n };\n const onUserMessage = (content: string): void =>\n deliver({ type: 'user_message', content, ...attr() });\n const onTextDelta = (delta: string): void => deliver({ type: 'text_delta', delta, ...attr() });\n const onToolStart = (state: IToolState): void =>\n deliver({ type: 'tool_start', state, ...attr() });\n const onToolEnd = (state: IToolState): void => deliver({ type: 'tool_end', state, ...attr() });\n const onThinking = (isThinking: boolean): void =>\n deliver({ type: 'thinking', isThinking, ...attr() });\n const onComplete = (result: IExecutionResult): void =>\n deliver({ type: 'complete', result: withoutHistory(result), ...attr() });\n const onInterrupted = (result: IExecutionResult): void =>\n deliver({ type: 'interrupted', result: withoutHistory(result), ...attr() });\n const onError = (error: Error): void =>\n deliver({\n type: 'error',\n message: error.message,\n ...classifySessionErrorForWire(error),\n ...attr(),\n });\n const onBackgroundTaskEvent = (event: TBackgroundTaskEvent): void =>\n deliver({ type: 'background_task_event', event });\n const onBackgroundJobGroupEvent = (event: TBackgroundJobGroupEvent): void =>\n deliver({ type: 'background_job_group_event', event });\n const onExecutionWorkspace = (event: IExecutionWorkspaceEvent): void =>\n deliver({\n type: 'execution_workspace_event',\n snapshot: event.snapshot,\n });\n const onPlanEvent = (event: IPlanApprovalEvent): void => deliver({ type: 'plan_event', event });\n const onContextFileRefreshed = (event: IContextFileRefreshedEvent): void =>\n deliver({ type: 'context_file_refreshed', event });\n const onBranchEvent = (event: IBranchEvent): void => deliver({ type: 'branch_event', event });\n // REMOTE-007: forward the transport-neutral prompt events so a remote surface can render + answer the\n // SAME permission/ask prompt; `prompt_resolved` dismisses it when another surface answered first.\n // #3189: kept while open, so a client that attaches later can ask for it with `get-prompts`.\n const receivePrompts = options.receivePrompts ?? true;\n const openPrompts = receivePrompts ? holdOpenPrompts(session) : undefined;\n const onPermissionRequest = (event: IPermissionRequestEvent): void => {\n const frame = { type: 'permission_request', event } as const;\n openPrompts?.record(frame);\n deliver(frame);\n };\n const onAskRequest = (event: IAskRequestEvent): void => {\n const frame = { type: 'ask_request', event } as const;\n openPrompts?.record(frame);\n deliver(frame);\n };\n const onPromptResolved = (event: IPromptResolvedEvent): void => {\n openPrompts?.forget(event.id);\n deliver({ type: 'prompt_resolved', event });\n };\n // CMD-004 Stage D: `ui_intent` is REQUESTER-ROUTED — delivered only to the surface whose\n // server-assigned driver id issued the command. An UNATTRIBUTED intent (no requester id, e.g. an\n // idle model-invoked command) is unroutable and reaches every surface — never a silent drop.\n const onUiIntent = (event: IUiIntentEvent): void => {\n if (\n event.requesterDriverId !== undefined &&\n event.requesterDriverId !== options.getSurfaceDriverId?.()\n ) {\n return; // another surface's intent — this surface never sees it (and never buffers it)\n }\n deliver({ type: 'ui_intent', event });\n };\n // CMD-004 Stage E: BROADCAST session-state events — the host executed the rename/clear; EVERY\n // attached surface (co-driving included) reflects it. Never requester-filtered, unlike `ui_intent`.\n const onSessionRenamed = (event: ISessionRenamedEvent): void =>\n deliver({ type: 'session_renamed', event });\n const onHistoryCleared = (): void => deliver({ type: 'history_cleared' });\n // #3189: this surface's session is now another one; it re-reads what it shows. Only surfaces\n // subscribed to the session that moved hear it: a host that binds each connection to its own session\n // sends it to the connection that switched, never to the others.\n const onSessionSwitched = (event: ISessionSwitchedEvent): void => {\n // This connection no longer answers the previous session's prompts; it reads the new session's anew.\n openPrompts?.clear();\n deliver({ type: 'session_switched', event });\n };\n // #3189: what a client that renders the whole session (the TUI) needs beside the streamed turn.\n // The context window is pushed in the frame `get-context` answers with. A compaction, a skill\n // activation or a memory event adds history entries no streamed frame carries, so the client is\n // told the history changed and re-reads it with `get-history`; the entries themselves are not sent.\n const onContextUpdate = (state: ReturnType<IProtocolSession['getContextState']>): void =>\n deliver({ type: 'context', state });\n const onHistoryChanged = (): void => deliver({ type: 'history_changed' });\n const onCompact = (event: ICompactEvent): void =>\n deliver({\n type: 'history_changed',\n compact: {\n trigger: event.trigger,\n beforeUsedPercentage: event.before.usedPercentage,\n afterUsedPercentage: event.after.usedPercentage,\n },\n });\n const onTurnSource = (source: TTurnSource): void => deliver({ type: 'turn_source', source });\n // One client's `/mode` or `/model` shows on every client on this session, in the frame\n // `get-status` answers with.\n const onStatusChanged = (status: ISessionStatusSnapshot): void =>\n deliver({ type: 'session_status', status });\n\n session.on('user_message', onUserMessage);\n session.on('text_delta', onTextDelta);\n session.on('tool_start', onToolStart);\n session.on('tool_end', onToolEnd);\n session.on('thinking', onThinking);\n session.on('complete', onComplete);\n session.on('interrupted', onInterrupted);\n session.on('error', onError);\n session.on('background_task_event', onBackgroundTaskEvent);\n session.on('background_job_group_event', onBackgroundJobGroupEvent);\n session.on('execution_workspace_event', onExecutionWorkspace);\n session.on('plan_event', onPlanEvent);\n session.on('context_file_refreshed', onContextFileRefreshed);\n session.on('branch_event', onBranchEvent);\n if (receivePrompts) {\n session.on('permission_request', onPermissionRequest);\n session.on('ask_request', onAskRequest);\n }\n session.on('prompt_resolved', onPromptResolved);\n session.on('ui_intent', onUiIntent);\n session.on('session_renamed', onSessionRenamed);\n session.on('history_cleared', onHistoryCleared);\n session.on('session_switched', onSessionSwitched);\n session.on('context_update', onContextUpdate);\n session.on('compact', onCompact);\n session.on('skill_activation', onHistoryChanged);\n session.on('memory_event', onHistoryChanged);\n session.on('turn_source', onTurnSource);\n session.on('status_changed', onStatusChanged);\n\n return (): void => {\n session.off('user_message', onUserMessage);\n session.off('text_delta', onTextDelta);\n session.off('tool_start', onToolStart);\n session.off('tool_end', onToolEnd);\n session.off('thinking', onThinking);\n session.off('complete', onComplete);\n session.off('interrupted', onInterrupted);\n session.off('error', onError);\n session.off('background_task_event', onBackgroundTaskEvent);\n session.off('background_job_group_event', onBackgroundJobGroupEvent);\n session.off('execution_workspace_event', onExecutionWorkspace);\n session.off('plan_event', onPlanEvent);\n session.off('context_file_refreshed', onContextFileRefreshed);\n session.off('branch_event', onBranchEvent);\n if (receivePrompts) {\n session.off('permission_request', onPermissionRequest);\n session.off('ask_request', onAskRequest);\n }\n session.off('prompt_resolved', onPromptResolved);\n session.off('ui_intent', onUiIntent);\n session.off('session_renamed', onSessionRenamed);\n session.off('history_cleared', onHistoryCleared);\n session.off('session_switched', onSessionSwitched);\n session.off('context_update', onContextUpdate);\n session.off('compact', onCompact);\n session.off('skill_activation', onHistoryChanged);\n session.off('memory_event', onHistoryChanged);\n session.off('turn_source', onTurnSource);\n session.off('status_changed', onStatusChanged);\n openPrompts?.release();\n };\n}\n\n/** The turn's result without the session's history: that only grows, and `get-history` pages it. */\nfunction withoutHistory(result: IExecutionResult): TWireExecutionResult {\n const { history: _history, ...rest } = result;\n return rest;\n}\n","import { listOpenPrompts } from './open-prompts.js';\n\nimport type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { IProtocolSession } from './protocol-session.js';\nimport type { IWireHistoryEntry, TClientMessage, TServerMessage } from './wire-messages.js';\n\ntype TSessionQueryMessage = Extract<\n TClientMessage,\n {\n type:\n | 'get-messages'\n | 'get-history'\n | 'get-prompts'\n | 'get-context'\n | 'get-commands'\n | 'get-status'\n | 'list-models'\n | 'get-agent-definitions'\n | 'get-executing'\n | 'get-pending'\n | 'get-execution-workspace'\n | 'read-execution-detail';\n }\n>;\n\n/**\n * #3189: the most serialized history one `history` frame carries. Well under the tightest outbound\n * budget (an attached terminal's, 1 MiB), so a client reading page after page is never mistaken for\n * one that stopped reading.\n */\nexport const HISTORY_PAGE_MAX_BYTES = 256 * 1024;\n\nconst utf8 = new TextEncoder();\n\nexport function isSessionQueryMessage(msg: TClientMessage): msg is TSessionQueryMessage {\n return (\n msg.type === 'get-messages' ||\n msg.type === 'get-history' ||\n msg.type === 'get-prompts' ||\n msg.type === 'get-context' ||\n msg.type === 'get-commands' ||\n msg.type === 'get-status' ||\n msg.type === 'list-models' ||\n msg.type === 'get-agent-definitions' ||\n msg.type === 'get-executing' ||\n msg.type === 'get-pending' ||\n msg.type === 'get-execution-workspace' ||\n msg.type === 'read-execution-detail'\n );\n}\n\nexport function handleSessionQueryMessage(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: TSessionQueryMessage,\n): void {\n if (msg.type === 'get-messages') {\n // #3288 §2: `display` rides the same frame as `messages` — a reload/reconnect/resume replay\n // needs both in one round trip, and there is no reason a client would ever want one without\n // the other (the raw messages already have to be fetched to build the projection).\n deliver({ type: 'messages', messages: session.getMessages(), display: session.getMessagesDisplay() });\n } else if (msg.type === 'get-history') {\n deliver(historyPage(session, msg.fromIndex ?? 0));\n } else if (msg.type === 'get-prompts') {\n for (const frame of listOpenPrompts(session)) deliver(frame);\n } else if (msg.type === 'get-context') {\n deliver({ type: 'context', state: session.getContextState() });\n } else if (msg.type === 'get-commands') {\n deliver({ type: 'commands', commands: session.listCommands(), skills: session.listSkills() });\n } else if (msg.type === 'get-status') {\n deliver({ type: 'session_status', status: session.getStatusSnapshot() });\n } else if (msg.type === 'list-models') {\n const snapshot = session.listModels();\n deliver({ type: 'model_list', requestId: msg.requestId, ...snapshot });\n } else if (msg.type === 'get-agent-definitions') {\n deliver({\n type: 'agent_definitions',\n requestId: msg.requestId,\n agents: session.listAgentDefinitions(),\n current: session.getDefaultAgentType(),\n });\n } else if (msg.type === 'get-executing') {\n deliver({ type: 'executing', executing: session.isExecuting() });\n } else if (msg.type === 'get-execution-workspace') {\n deliver({\n type: 'execution_workspace_event',\n snapshot: session.getExecutionWorkspaceSnapshot(),\n });\n } else if (msg.type === 'read-execution-detail') {\n readExecutionDetail(session, deliver, msg);\n } else {\n deliver(pendingFrame(session));\n }\n}\n\n/** One page of an execution-workspace entry's detail, answered under the request's id. */\nfunction readExecutionDetail(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: Extract<TClientMessage, { type: 'read-execution-detail' }>,\n): void {\n const { requestId } = msg;\n session.readExecutionWorkspaceDetail(msg.entryId, msg.cursor).then(\n (page) => deliver({ type: 'execution_detail', requestId, page }),\n (error: unknown) =>\n deliver({\n type: 'execution_detail_error',\n requestId,\n message: error instanceof Error ? error.message : String(error),\n }),\n );\n}\n\n/** The next queued prompt and how many wait, as the session reports them now. */\nexport function pendingFrame(\n session: IProtocolSession,\n): Extract<TServerMessage, { type: 'pending' }> {\n return {\n type: 'pending',\n pending: session.getPendingPrompt(),\n pendingCount: session.getPendingCount(),\n };\n}\n\n/**\n * The history from `fromIndex`, as many entries as fit {@link HISTORY_PAGE_MAX_BYTES}. At least one\n * entry is sent while any remain, so a client always moves forward, even past one larger entry.\n */\nfunction historyPage(\n session: IProtocolSession,\n fromIndex: number,\n): Extract<TServerMessage, { type: 'history' }> {\n const history = session.getFullHistory();\n const total = history.length;\n const startIndex = Math.min(fromIndex, total);\n const entries: IWireHistoryEntry[] = [];\n let bytes = 0;\n for (let index = startIndex; index < total; index += 1) {\n const entry = toWireHistoryEntry(history[index]!);\n const size = utf8.encode(JSON.stringify(entry)).byteLength;\n if (entries.length > 0 && bytes + size > HISTORY_PAGE_MAX_BYTES) break;\n entries.push(entry);\n bytes += size;\n }\n return { type: 'history', startIndex, total, entries };\n}\n\nfunction toWireHistoryEntry(\n entry: ReturnType<IProtocolSession['getFullHistory']>[number],\n): IWireHistoryEntry {\n return { ...entry, timestamp: toIsoTimestamp(entry.timestamp) };\n}\n\n/**\n * The declared type is `Date`, but a resumed session's entries were read back from JSON unrevived, so\n * their `timestamp` is already the ISO string the store wrote. Either way the wire carries that string.\n */\nfunction toIsoTimestamp(timestamp: Date | string): string {\n return typeof timestamp === 'string' ? timestamp : timestamp.toISOString();\n}\n","import type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { IProtocolSession } from './protocol-session.js';\nimport type { TClientMessage } from './wire-messages.js';\nimport type { TCommandSurfaceLocality } from '@robota-sdk/agent-interface-command';\nimport type { ISettingsSnapshot, TSettingsPatch } from '@robota-sdk/agent-interface-session';\n\ntype TSettingsMessage = Extract<TClientMessage, { type: 'get-settings' | 'update-settings' }>;\n\nexport function isSettingsMessage(msg: TClientMessage): msg is TSettingsMessage {\n return msg.type === 'get-settings' || msg.type === 'update-settings';\n}\n\n/** What applying a patch did: the fresh snapshot, or why nothing was written. */\nexport type TSettingsUpdateOutcome =\n | { readonly ok: true; readonly settings: ISettingsSnapshot }\n | {\n readonly ok: false;\n readonly code: 'not_available' | 'invalid' | 'refused' | 'update_failed';\n readonly message: string;\n };\n\n/**\n * #3282 §4a: host-owned read/write for the Settings screen, supplied by the composition root the\n * same way `IUsageQueryReporters` supplies usage reads — this package only correlates the request\n * and carries the result. `updateSettings` applies through the SAME function the matching slash\n * command uses (issue #3282 §4: \"one code path, so the behavior matches the TUI\"), so whatever a\n * command would refuse on this surface is refused here too.\n */\nexport interface ISettingsReporter {\n getSettings(\n session: IProtocolSession,\n locality?: TCommandSurfaceLocality,\n ): ISettingsSnapshot | Promise<ISettingsSnapshot>;\n /**\n * Applies through the SAME function the matching slash command uses. `locality` (#3282 §4 part\n * b-2) is forwarded to that command exactly as the transport received it, so a command that\n * refuses a remote surface (installing a plugin) refuses identically here.\n */\n updateSettings(\n session: IProtocolSession,\n patch: TSettingsPatch,\n locality?: TCommandSurfaceLocality,\n ): TSettingsUpdateOutcome | Promise<TSettingsUpdateOutcome>;\n}\n\nexport function handleSettingsMessage(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: TSettingsMessage,\n reporter: ISettingsReporter | undefined,\n locality?: TCommandSurfaceLocality,\n): void {\n if (!reporter) {\n deliverSettingsError(\n deliver,\n msg.requestId,\n 'not_available',\n 'Settings are not available on this host.',\n );\n return;\n }\n try {\n if (msg.type === 'get-settings') {\n void Promise.resolve(reporter.getSettings(session, locality)).then(\n (settings) => deliver({ type: 'settings', requestId: msg.requestId, settings }),\n (error) =>\n deliverSettingsError(deliver, msg.requestId, 'update_failed', failureMessage(error)),\n );\n return;\n }\n void Promise.resolve(reporter.updateSettings(session, msg.patch, locality)).then(\n (outcome) => {\n if (outcome.ok) {\n deliver({ type: 'settings', requestId: msg.requestId, settings: outcome.settings });\n } else {\n deliverSettingsError(deliver, msg.requestId, outcome.code, outcome.message);\n }\n },\n (error) =>\n deliverSettingsError(deliver, msg.requestId, 'update_failed', failureMessage(error)),\n );\n } catch (error) {\n deliverSettingsError(\n deliver,\n msg.requestId,\n 'update_failed',\n failureMessage(error instanceof Error ? error : String(error)),\n );\n }\n}\n\nfunction deliverSettingsError(\n deliver: TOutboundDeliver,\n requestId: string,\n code: 'not_available' | 'invalid' | 'refused' | 'update_failed',\n message: string,\n): void {\n deliver({ type: 'settings_error', requestId, code, message });\n}\n\nfunction failureMessage(error: Error | string): string {\n return error instanceof Error ? error.message : String(error);\n}\n","/**\n * Carrier-neutral session message adapter.\n *\n * Framework-agnostic: works with any transport implementation via\n * send/onMessage callbacks. No dependency on a carrier library.\n *\n * Protocol: JSON messages with { type, ...payload } structure.\n * Server pushes IProtocolSession events to client in real-time.\n */\n\nimport {\n handleAppToolMessage,\n handleAppViewMessage,\n isAppToolMessage,\n isAppViewMessage,\n type IAppMessageOptions,\n} from './app-tool-messages.js';\nimport {\n handleBackgroundControlMessage,\n handleBackgroundQueryMessage,\n} from './background-messages.js';\nimport { handleLoopControlMessage, isLoopControlMessage } from './loop-control-messages.js';\nimport { parseClientMessage } from './message-parser.js';\nimport { isObserverMessageType } from './observer-messages.js';\nimport { handleProjectReadMessage, isProjectReadMessage } from './project-read-messages.js';\nimport {\n handleSessionDeleteMessage,\n handleSessionDirectoryMessage,\n handleSessionRenameMessage,\n isSessionDeleteMessage,\n isSessionDirectoryMessage,\n isSessionRenameMessage,\n} from './session-directory-messages.js';\nimport { subscribeSessionEvents } from './session-events.js';\nimport {\n handleSessionQueryMessage,\n isSessionQueryMessage,\n pendingFrame,\n} from './session-query-messages.js';\nimport { handleSettingsMessage, isSettingsMessage } from './settings-messages.js';\nimport { handleUsageQueryMessage } from './usage-messages.js';\n\nimport type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { IProtocolSession, TProjectReadCapableSession } from './protocol-session.js';\nimport type { ISettingsReporter } from './settings-messages.js';\nimport type { IUsageQueryReporters } from './usage-messages.js';\nimport type { TClientMessage } from './wire-messages.js';\nimport type { TCommandSurfaceLocality } from '@robota-sdk/agent-interface-command';\nimport type { TUsageSurface } from '@robota-sdk/agent-interface-analytics';\nimport type { ISessionDirectory, TDriverId } from '@robota-sdk/agent-interface-session';\n\n// Outbound session→TServerMessage fan-out (incl. CMD-004 requester-routed `ui_intent`) lives in\n// `session-events.ts`; re-exported here for the bridge and existing importers.\nexport { subscribeSessionEvents } from './session-events.js';\nexport type { ISubscribeSessionEventsOptions } from './session-events.js';\nexport { parseClientMessage } from './message-parser.js';\n\n/**\n * What a connected surface may do. `drive` sends prompts, answers questions and controls the session.\n * `observe` is read-only: it follows this session's conversation and state, but never submits, answers,\n * controls, or reads another session's records, and it never counts as a surface that can answer.\n */\nexport type TSessionSurfaceRole = 'drive' | 'observe';\n\nexport interface ISessionMessageHandlerOptions {\n /** IProtocolSession to expose — `Partial<ISessionProjectRead>` is probed, not assumed (#3282 §4c). */\n session: TProjectReadCapableSession;\n /**\n * ARCH-030: the CARRIER's connection-scoped outbound delivery boundary — not a raw `send`, and not a\n * `send` plus an error callback for this handler to assemble into one. The carrier owns both the sink\n * and the \"what does a failed send mean for this connection\" policy, so it builds the boundary and\n * passes it down; the raw sink never crosses this parameter, which is what stops a future reply family\n * from reaching the wire unguarded.\n */\n deliver: TOutboundDeliver;\n /**\n * REMOTE-014 E5: the SERVER-ASSIGNED driver id for THIS remote surface (the E3 `deviceId`). Injected into\n * every inbound `submit`/`command`/prompt-response so a co-drive turn/answer is attributed to this driver —\n * a client-supplied driver id is NEVER trusted. Absent → unattributed (the session defaults to the owner).\n */\n driverId?: TDriverId;\n /** Trusted carrier-owned product surface, kept separate from driver identity. */\n surface?: TUsageSurface;\n /** Carrier-decided role of this connection; defaults to `drive`. The client never chooses it here. */\n role?: TSessionSurfaceRole;\n /** Host-owned cross-session read model. The protocol only correlates and carries its result. */\n personalUsageReporter?: NonNullable<IUsageQueryReporters['personalUsageReporter']>;\n /** Host-owned current-session trace/cost producer for the pre-existing message family. */\n usageReporter?: NonNullable<IUsageQueryReporters['usageReporter']>;\n /** Host-owned stored-session producer used by cross-session drill-down. */\n storedSessionUsageReporter?: NonNullable<IUsageQueryReporters['storedSessionUsageReporter']>;\n /** Host-owned session directory (#3189): list, start and switch the host's sessions. */\n sessionDirectory?: ISessionDirectory;\n /** #3282 §4a: host-owned read/write for the GUI Settings screen. */\n settingsReporter?: ISettingsReporter;\n /**\n * #3282 §4 part b-2: carrier-decided, like `role` — whether THIS carrier's connections are\n * provably on this machine. The loopback WS carrier is `'local'`; the device-mesh/WebRTC carrier\n * sets `'remote'`. Absent → `'local'`, forwarded to `executeCommand` and the settings reporter so a\n * command that runs code from outside the session (installing a plugin) can refuse a remote device.\n */\n commandSurfaceLocality?: TCommandSurfaceLocality;\n /**\n * The most bytes one frame answering an app tool call or view read may take, for a carrier whose\n * channel carries less than the inbound limit (a WebRTC data channel). An answer over it is\n * replaced by a `protocol_error` before it reaches any buffer. Defaults to the inbound limit less\n * a margin for a carrier that stamps frames afterwards.\n */\n maxFrameBytes?: number;\n}\n\n/**\n * Create a carrier-neutral message handler for an IProtocolSession.\n *\n * Returns:\n * - `onMessage(data)`: call this when the WebSocket receives a message\n * - `cleanup()`: call this when the WebSocket disconnects\n *\n * Usage:\n * ```typescript\n * const delivery = createOutboundDelivery(\n * (msg) => ws.send(JSON.stringify(msg)),\n * (error) => ws.close(1011, error.message),\n * );\n * const { onMessage, cleanup } = createSessionMessageHandler({ session: interactiveSession, deliver: delivery });\n *\n * ws.on('message', (data) => onMessage(String(data)));\n * ws.on('close', cleanup);\n * ```\n */\nexport function createSessionMessageHandler(options: ISessionMessageHandlerOptions): {\n onMessage: (data: string) => void;\n cleanup: () => void;\n} {\n const role = options.role ?? 'drive';\n const unsubscribe = subscribeSessionEvents(options.session, options.deliver, {\n getSurfaceDriverId: () => options.driverId,\n receivePrompts: role === 'drive',\n });\n // One per connection: when the connection goes, work it started for a hosted app view (a tool call\n // waiting on its permission prompt) is cancelled, so the prompt is withdrawn instead of left open.\n const connection = new AbortController();\n const cleanup = (): void => {\n connection.abort();\n unsubscribe();\n };\n const onMessage = createMessageHandler(\n options.session,\n options.deliver,\n options.driverId,\n {\n personalUsageReporter: options.personalUsageReporter,\n usageReporter: options.usageReporter,\n storedSessionUsageReporter: options.storedSessionUsageReporter,\n },\n options.surface,\n role,\n options.sessionDirectory,\n options.settingsReporter,\n options.commandSurfaceLocality,\n {\n signal: connection.signal,\n ...(options.maxFrameBytes !== undefined ? { maxFrameBytes: options.maxFrameBytes } : {}),\n },\n );\n\n return { onMessage, cleanup };\n}\n\nfunction createMessageHandler(\n session: TProjectReadCapableSession,\n deliver: TOutboundDeliver,\n driverId?: TDriverId,\n reporters: IUsageQueryReporters = EMPTY_USAGE_REPORTERS,\n surface?: TUsageSurface,\n role: TSessionSurfaceRole = 'drive',\n sessionDirectory?: ISessionDirectory,\n settingsReporter?: ISettingsReporter,\n commandSurfaceLocality?: TCommandSurfaceLocality,\n appMessages?: IAppMessageOptions,\n): (data: string) => void {\n return (data: string): void => {\n const msg = parseClientMessage(data, deliver);\n if (!msg) return;\n if (role === 'observe' && !isObserverMessageType(msg.type)) {\n deliver({ type: 'protocol_error', message: `Not permitted for an observer: ${msg.type}` });\n return;\n }\n handleClientMessage(\n session,\n deliver,\n msg,\n driverId,\n reporters,\n surface,\n sessionDirectory,\n settingsReporter,\n commandSurfaceLocality,\n appMessages,\n );\n };\n}\n\nconst EMPTY_USAGE_REPORTERS: IUsageQueryReporters = {\n personalUsageReporter: undefined,\n usageReporter: undefined,\n storedSessionUsageReporter: undefined,\n};\n\n/**\n * Route a parsed client message to the session (control/query/background/prompt-response). Exported for E4:\n * the {@link SessionResumeBridge} intercepts `resume`/`ack` itself and delegates everything else here.\n */\nexport function handleClientMessage(\n session: TProjectReadCapableSession,\n deliver: TOutboundDeliver,\n msg: TClientMessage,\n driverId?: TDriverId,\n reporters: IUsageQueryReporters = EMPTY_USAGE_REPORTERS,\n surface?: TUsageSurface,\n sessionDirectory?: ISessionDirectory,\n settingsReporter?: ISettingsReporter,\n commandSurfaceLocality?: TCommandSurfaceLocality,\n appMessages?: IAppMessageOptions,\n): void {\n if (handleUsageQueryMessage(session, deliver, msg, reporters)) {\n return;\n }\n if (isSessionDirectoryMessage(msg)) {\n handleSessionDirectoryMessage(deliver, msg, sessionDirectory);\n return;\n }\n if (isSettingsMessage(msg)) {\n handleSettingsMessage(session, deliver, msg, settingsReporter, commandSurfaceLocality);\n return;\n }\n if (isSessionRenameMessage(msg)) {\n handleSessionRenameMessage(deliver, msg, sessionDirectory);\n return;\n }\n if (isSessionDeleteMessage(msg)) {\n handleSessionDeleteMessage(deliver, msg, sessionDirectory);\n return;\n }\n if (isSessionControlMessage(msg)) {\n handleSessionControlMessage(session, deliver, msg, driverId, surface, commandSurfaceLocality);\n return;\n }\n if (isLoopControlMessage(msg)) {\n handleLoopControlMessage(session, deliver, msg);\n return;\n }\n if (isSessionQueryMessage(msg)) {\n handleSessionQueryMessage(session, deliver, msg);\n return;\n }\n if (isProjectReadMessage(msg)) {\n handleProjectReadMessage(session, deliver, msg);\n return;\n }\n if (isAppToolMessage(msg)) {\n handleAppToolMessage(session, deliver, msg, driverId, appMessages);\n return;\n }\n if (isAppViewMessage(msg)) {\n handleAppViewMessage(session, deliver, msg, appMessages);\n return;\n }\n if (isBackgroundQueryMessage(msg)) {\n handleBackgroundQueryMessage(session, deliver, msg);\n return;\n }\n if (isBackgroundControlMessage(msg)) {\n handleBackgroundControlMessage(session, deliver, msg);\n return;\n }\n if (isPromptResponseMessage(msg)) {\n handlePromptResponseMessage(session, msg, driverId);\n return;\n }\n const unknownType = (msg as { type: string }).type;\n deliver({ type: 'protocol_error', message: `Unknown message type: ${unknownType}` });\n}\n\nfunction isSessionControlMessage(\n msg: TClientMessage,\n): msg is Extract<TClientMessage, { type: 'submit' | 'command' | 'abort' | 'cancel-queue' }> {\n return (\n msg.type === 'submit' ||\n msg.type === 'command' ||\n msg.type === 'abort' ||\n msg.type === 'cancel-queue'\n );\n}\n\nfunction isBackgroundQueryMessage(\n msg: TClientMessage,\n): msg is Extract<\n TClientMessage,\n | { type: 'get-background-tasks' | 'get-background-task' | 'read-background-task-log' }\n | { type: 'get-background-job-groups' | 'get-background-job-group' | 'wait-background-job-group' }\n> {\n return (\n msg.type === 'get-background-tasks' ||\n msg.type === 'get-background-task' ||\n msg.type === 'read-background-task-log' ||\n msg.type === 'get-background-job-groups' ||\n msg.type === 'get-background-job-group' ||\n msg.type === 'wait-background-job-group'\n );\n}\n\nfunction isBackgroundControlMessage(\n msg: TClientMessage,\n): msg is Extract<\n TClientMessage,\n { type: 'cancel-background-task' | 'close-background-task' | 'send-background-task' }\n> {\n return (\n msg.type === 'cancel-background-task' ||\n msg.type === 'close-background-task' ||\n msg.type === 'send-background-task'\n );\n}\n\nfunction isPromptResponseMessage(\n msg: TClientMessage,\n): msg is Extract<TClientMessage, { type: 'permission-response' | 'ask-response' }> {\n return msg.type === 'permission-response' || msg.type === 'ask-response';\n}\n\n/**\n * REMOTE-007: a driving client answered a pending prompt by id. `resolvePermission`/`resolveAsk` are\n * idempotent — a stale id (already answered by another surface, or drained) is a safe no-op, so no\n * acknowledgement is needed; the resulting `prompt_resolved` server event is the shared signal.\n */\nfunction handlePromptResponseMessage(\n session: IProtocolSession,\n msg: Extract<TClientMessage, { type: 'permission-response' | 'ask-response' }>,\n driverId?: TDriverId,\n): void {\n // REMOTE-014 E5: record the SERVER-ASSIGNED answering driver (not client-sent) on `prompt_resolved`.\n if (msg.type === 'permission-response') {\n session.resolvePermission(msg.id, msg.result, driverId);\n } else {\n session.resolveAsk(msg.id, msg.response, driverId);\n }\n}\n\nfunction handleSessionControlMessage(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: Extract<TClientMessage, { type: 'submit' | 'command' | 'abort' | 'cancel-queue' }>,\n driverId?: TDriverId,\n surface?: TUsageSurface,\n commandSurfaceLocality?: TCommandSurfaceLocality,\n): void {\n if (msg.type === 'submit') {\n // TRANS-008 (issue #2045). A TYPE check, not a falsy one: `{}`, `[]`, `42` and `true` are truthy\n // and passed the previous `!msg.prompt` guard. It matters more here than at the other payload\n // fields because the value does not stop at this session — it is re-emitted as `user_message` to\n // EVERY attached client inside a frame whose `content` is declared `string`.\n if (typeof msg.prompt !== 'string' || msg.prompt.length === 0) {\n deliver({ type: 'protocol_error', message: 'prompt must be a non-empty string' });\n return;\n }\n // REMOTE-014 E5: attribute this remote turn to the SERVER-ASSIGNED driver id (never a client-sent one).\n const submitOptions = {\n ...(driverId ? { driverId } : {}),\n ...(surface ? { surface } : {}),\n };\n session\n .submit(\n msg.prompt,\n undefined,\n undefined,\n Object.keys(submitOptions).length > 0 ? submitOptions : undefined,\n )\n .then(\n // #3189: a prompt submitted during a turn resolves once it is queued. A `get-pending` sent\n // beside the `submit` would be answered before that, so the queue is reported from here.\n () => deliver(pendingFrame(session)),\n (error: Error) => deliver({ type: 'protocol_error', message: error.message }),\n );\n } else if (msg.type === 'command') {\n const request = msg.requestId !== undefined ? { requestId: msg.requestId } : {};\n if (!msg.name) {\n deliver({ type: 'protocol_error', message: 'name is required', ...request });\n return;\n }\n // REMOTE-003: a transport-origin command is tagged `'remote'` (optional policy, allow-by-default;\n // REMOTE-006). CMD-004: the SERVER-ASSIGNED driver id (E5) is the command origin — intents route back here.\n session.executeCommand(msg.name, msg.args ?? '', 'remote', driverId, commandSurfaceLocality).then(\n (result) => {\n deliver({\n type: 'command_result',\n name: msg.name,\n message: result?.message ?? `Unknown command: ${msg.name}`,\n success: result?.success ?? false,\n data: result?.data,\n ...request,\n });\n },\n (error: Error) => {\n deliver({ type: 'protocol_error', message: error.message, ...request });\n },\n );\n } else if (msg.type === 'abort') {\n session.abort();\n } else {\n session.cancelQueue();\n }\n}\n","/**\n * The connection-scoped outbound delivery boundary (ARCH-030).\n *\n * Every outbound `TServerMessage` on one connection — session-event fan-out AND every reply to an\n * inbound frame — goes through ONE boundary that owns carrier-failure containment. Before this\n * existed, `subscribeSessionEvents` guarded its sends while the reply paths received the raw carrier\n * `send`, so a reply resolving after a disconnect threw from a Promise continuation, escaped as an\n * unhandled rejection, and left the carrier's cleanup — written, idempotent, and waiting — unnotified.\n *\n * WHO BUILDS IT. The **carrier**, from its own private sink and its own failure policy, and passes the\n * result down into the protocol handler. Not the other way round: the raw sink and the \"what does a\n * failed send mean for this connection\" decision both belong to the carrier, and handing the protocol\n * layer a raw sink so it can hand a wrapper back leaves the raw sink reachable — which is the hole a\n * twelfth reply family walks through next year.\n *\n * WHY IT IS BRANDED. {@link createOutboundDelivery} is the only producer of {@link TOutboundDeliver}, so\n * a plain `(message: TServerMessage) => void` is **refused by the compiler** wherever a boundary is\n * required. That turns \"a new raw-send continuation cannot be added silently\" from a scan that a rename\n * defeats into a type error at the call site. `src/__tests__` is inside this package's `tsconfig.json`\n * `include`, so the `@ts-expect-error` case pinning it is checked by `tsgo --noEmit`.\n *\n * IT LATCHES. A boundary reports AT MOST ONE delivery failure; after the first, it is closed and every\n * subsequent frame is dropped without a further report. All three carriers already treat a delivery\n * failure as terminal (`WsSessionDelivery.close` → `socket.close(1011)`, `WebRtcDeliveryLifecycle` →\n * `channel.close()`, `PairingGate` → `pairingChannel.close`) and each had grown its own latch to\n * suppress the repeats; the latch belongs upstream of all three. `SessionResumeBridge` already behaved\n * this way, so the handler path converges onto the bridge's semantics rather than the reverse.\n *\n * A boundary is not reusable across carriers: a latched one stays latched. `SessionResumeBridge` builds\n * a fresh boundary per `attach`, which is what un-latches the session after a reconnect.\n */\n\nimport type { TServerMessage } from './wire-messages.js';\n\ndeclare const outboundDeliveryBrand: unique symbol;\n\n/**\n * Deliver one outbound frame on a connection. A carrier failure is REPORTED through the boundary's\n * error handler, never thrown at the caller — so a reply resolving after a disconnect cannot escape as\n * an unhandled rejection or out of the carrier's inbound listener.\n *\n * Only {@link createOutboundDelivery} produces one. The brand is the mechanism, not decoration: it is\n * what makes a raw carrier `send` unusable where a boundary is required.\n */\nexport type TOutboundDeliver = ((message: TServerMessage) => void) & {\n readonly [outboundDeliveryBrand]: true;\n\n /**\n * Bytes this carrier has ACCEPTED and not yet written to the peer — or `undefined` when it cannot\n * say.\n *\n * `deliver` returning is not delivery. Both carriers hand this boundary a fire-and-forget sink, so\n * a frame that has \"been sent\" may be sitting in a socket buffer the boundary cannot see. Anything\n * this boundary counted itself would be a count of what it HANDED OVER, which is a different\n * quantity from what the peer has not read — and a budget over the wrong quantity reports a\n * healthy connection for a peer that stopped reading (ARCH-030 / issue #1734).\n *\n * `undefined` is deliberately not `0`. A carrier that cannot report backpressure and a carrier with\n * nothing pending are different states, and collapsing them would let \"unknown\" satisfy any\n * threshold placed on this number.\n */\n readonly pendingBytes: () => number | undefined;\n};\n\n/**\n * Observe THIS connection's first outbound delivery failure. `event` is the `type` of the frame that\n * could not be delivered — for the fan-out, the frame a session event was forwarded as (usually, not\n * always, the event's own name), and the reply's type for a reply (`command_result`, `protocol_error`, …).\n *\n * Required, never optional: a carrier that could opt out of observing its own delivery failures is the\n * silent-failure shape this boundary exists to remove.\n */\nexport type TDeliveryErrorHandler = (error: Error, event: TServerMessage['type']) => void;\n\n/**\n * Build the outbound delivery boundary for ONE connection from that connection's raw sink and its\n * failure policy.\n *\n * @param send - the carrier's raw sink. May throw; that is what makes it raw.\n * @param onDeliveryError - the carrier's failure policy, invoked at most once. A handler that itself\n * throws is isolated: a diagnostic cannot reverse an already-committed session operation.\n * @param pendingBytes - the carrier's own backpressure reading. Optional because not every carrier\n * has one; omitted, the boundary answers `undefined` rather than inventing a number.\n */\n/**\n * Default outbound backpressure budget, in bytes the carrier has accepted and not yet written.\n *\n * 8 MiB. The number is a policy, not a measurement, and it is stated here rather than inlined so a\n * reader can find the one place that decides it. It is well above any single frame this protocol\n * produces and well below a figure at which a non-reading peer could exhaust the host — the property\n * that matters is that SOME finite budget exists, because the previous behaviour was unbounded.\n *\n * A carrier that cannot report `pendingBytes` is not subject to it: `undefined` is unknown, not zero,\n * so no threshold applies to it. That is deliberate and it is a gap — a carrier with no backpressure\n * reading has no budget — recorded rather than hidden behind a default of `0`, which would refuse\n * every frame on such a carrier.\n */\nconst BYTES_PER_MIB = Number('1024') * Number('1024');\nexport const DEFAULT_MAX_PENDING_BYTES = Number('8') * BYTES_PER_MIB;\n\n/**\n * Backpressure budget for a terminal attached to a session on the same host, in either role.\n *\n * 1 MiB, tighter than the default: an attached terminal reads a local socket, so a backlog this large\n * means it stopped reading rather than that a network is slow. Exceeding it closes that connection\n * only; the session never waits for a reader, so other surfaces keep streaming.\n */\nexport const ATTACHED_SURFACE_MAX_PENDING_BYTES = BYTES_PER_MIB;\n\n/**\n * Is this carrier holding more than the budget allows?\n *\n * Exported because a WebSocket carries TWO kinds of outbound frame over one socket — the text\n * protocol and TRANS-001's payload channels — and only one of them goes through\n * {@link createOutboundDelivery}. A budget the binary path re-implemented would be a second opinion\n * about one socket's backpressure; a budget it skipped would leave the same non-reading peer\n * unbounded on the other half of the connection.\n *\n * `undefined` is unknown, not zero: a carrier that cannot report backpressure is never over budget.\n */\nexport function isOverPendingBudget(\n pending: number | undefined,\n limit: number = DEFAULT_MAX_PENDING_BYTES,\n): boolean {\n return pending !== undefined && pending > limit;\n}\n\n/**\n * Default outbound DRAIN budget (issue #2306): how long a carrier may stay non-empty before the peer\n * is treated as not reading.\n *\n * 120 seconds. The byte budget bounds HOW MUCH a peer holds; it cannot bound HOW LONG, because\n * `bufferedAmount` is a quantity with no history — a peer draining just enough to stay under 8 MiB\n * forever is never over it. The figure is generous on purpose: a legitimately slow link drains to\n * zero between bursts, so the clock resets, whereas a peer that keeps SOMETHING pending for two\n * minutes straight is holding a session, its resume buffer, and a socket it does not intend to read.\n * A badly chosen figure here disconnects real users, which is why it is one named constant and an\n * argument, never an inline number. `Infinity` disables the budget.\n */\nexport const DEFAULT_MAX_PENDING_MS = Number('120') * Number('1000');\n\n/**\n * The drain clock the boundary keeps for a carrier (issue #2306). One timestamp, not per-frame\n * bookkeeping: it starts on the first reading that finds the carrier non-empty, RESETS only when a\n * reading finds it empty (a peer that drains to one byte and stalls again has not drained), and is\n * consulted synchronously at each send — the boundary has no timer and gains none here.\n */\nexport interface IPendingStallClock {\n readonly maxPendingMs: number;\n /**\n * Record one reading of the carrier's pending bytes. Returns how long the carrier has been\n * continuously non-empty when that exceeds the budget; `undefined` otherwise. An `undefined`\n * reading is unknown, not zero: it neither starts nor resets the clock.\n */\n observe: (pending: number | undefined) => number | undefined;\n}\n\nexport function createPendingStallClock(\n maxPendingMs: number = DEFAULT_MAX_PENDING_MS,\n now: () => number = Date.now,\n): IPendingStallClock {\n let nonEmptySince: number | undefined;\n return {\n maxPendingMs,\n observe(pending) {\n if (pending === undefined) return undefined;\n if (pending === 0) {\n nonEmptySince = undefined;\n return undefined;\n }\n const at = now();\n nonEmptySince ??= at;\n const stalledMs = at - nonEmptySince;\n return stalledMs > maxPendingMs ? stalledMs : undefined;\n },\n };\n}\n\nexport function createOutboundDelivery(\n send: (message: TServerMessage) => void,\n onDeliveryError: TDeliveryErrorHandler,\n pendingBytes?: () => number | undefined,\n maxPendingBytes: number = DEFAULT_MAX_PENDING_BYTES,\n stallClock: IPendingStallClock = createPendingStallClock(),\n): TOutboundDeliver {\n let closed = false;\n const deliver = (message: TServerMessage): void => {\n if (closed) return;\n // The budget is checked BEFORE the send, not after: a peer that has stopped reading is detected\n // by what the carrier is still holding, and adding one more frame first would make the boundary\n // the last contributor to the overflow it is reporting.\n const pending = pendingBytes?.();\n if (isOverPendingBudget(pending, maxPendingBytes)) {\n closed = true;\n reportFailure(\n new Error(\n `outbound backpressure budget exceeded: ${pending} byte(s) pending, limit ${maxPendingBytes}. ` +\n 'The peer has accepted frames it is not reading.',\n ),\n message.type,\n );\n return;\n }\n // Issue #2306: the same reading, judged by duration. A peer under the byte budget that has kept\n // the carrier non-empty for longer than the drain budget is not reading either.\n const stalledMs = stallClock.observe(pending);\n if (stalledMs !== undefined) {\n closed = true;\n reportFailure(\n new Error(\n `outbound drain budget exceeded: ${pending} byte(s) pending for ${stalledMs} ms, limit ${stallClock.maxPendingMs} ms. ` +\n 'The peer is reading too slowly to ever drain what it accepted.',\n ),\n message.type,\n );\n return;\n }\n try {\n send(message);\n } catch (error) {\n // allow-fallback: this IS the boundary — a carrier send failure is a CARRIER lifecycle failure,\n // reported to the carrier's own policy (which closes the connection). It is never swallowed, and\n // re-throwing it would fail a session operation that has already committed.\n closed = true;\n reportFailure(error instanceof Error ? error : new Error(String(error)), message.type);\n }\n };\n function reportFailure(error: Error, event: TServerMessage['type']): void {\n try {\n onDeliveryError(error, event);\n } catch {\n // allow-fallback: the carrier-owned diagnostic is the LAST step of a failure already being\n // reported. A throw from it has nowhere left to go, and letting it out would make a committed\n // session operation fail for the sake of a diagnostic.\n }\n }\n return Object.assign(deliver, {\n pendingBytes: (): number | undefined => pendingBytes?.(),\n }) as TOutboundDeliver;\n}\n","/**\n * Bounded un-acked output buffer for session-resume (REMOTE-013 Stage E4).\n *\n * Holds the tail of outbound `TServerMessage`s that a reconnecting client may not have received, keyed by a\n * monotonic sequence number. On reconnect the client sends `resume{lastSeq}` and the host replays\n * {@link ResumeBuffer.tailAfter}; a periodic `ack{seq}` frees everything up to `seq`. The buffer is bounded by\n * a frame count AND a byte budget (drop-oldest) so a never-acking or gone peer cannot exhaust host memory —\n * and when a client's `lastSeq` predates the oldest retained frame it returns an **overrun** marker so the\n * client does a full refresh rather than accept a silent gap.\n */\n\nimport type { TServerMessage } from './wire-messages.js';\n\n/** One retained frame: its sequence number and the message. */\nexport interface IBufferedFrame {\n readonly seq: number;\n readonly message: TServerMessage;\n}\n\n/** The result of a `tailAfter` — either the ordered replay slice, or an overrun (client must full-refresh). */\nexport type TResumeTail =\n | { readonly kind: 'tail'; readonly frames: readonly IBufferedFrame[] }\n | { readonly kind: 'overrun' };\n\nexport interface IResumeBufferOptions {\n /** Max retained frames (drop-oldest beyond this). Default 512. */\n readonly maxFrames?: number;\n /** Max retained bytes across frames (drop-oldest beyond this). Default 4 MiB. */\n readonly maxBytes?: number;\n /** Serialized size of a message (for the byte budget). Default: JSON length. Injectable for tests. */\n readonly sizeOf?: (message: TServerMessage) => number;\n}\n\nconst DEFAULT_MAX_FRAMES = 512;\nconst DEFAULT_MAX_BYTES = 4 * 1024 * 1024;\n\n/**\n * A monotonic, bounded ring of un-acked frames. Sequence numbers are assigned by {@link append} and never\n * reused; `tailAfter` / `ackThrough` operate over the retained window.\n */\nexport class ResumeBuffer {\n private readonly frames: IBufferedFrame[] = [];\n private readonly sizes: number[] = []; // parallel to `frames` — retained byte size per frame\n private nextSeq = 1;\n private bytes = 0;\n\n private readonly maxFrames: number;\n private readonly maxBytes: number;\n private readonly sizeOf: (message: TServerMessage) => number;\n\n public constructor(options: IResumeBufferOptions = {}) {\n this.maxFrames = options.maxFrames ?? DEFAULT_MAX_FRAMES;\n this.maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n this.sizeOf = options.sizeOf ?? ((m) => JSON.stringify(m).length);\n }\n\n /** Append a message, assign + return its monotonic seq, and evict oldest frames beyond the bounds. */\n public append(message: TServerMessage): number {\n const seq = this.nextSeq;\n this.nextSeq += 1;\n const size = this.sizeOf(message);\n this.frames.push({ seq, message });\n this.sizes.push(size);\n this.bytes += size;\n this.evict();\n return seq;\n }\n\n /** Drop all retained frames with `seq <= throughSeq` (the client confirmed receipt up to there). */\n public ackThrough(throughSeq: number): void {\n let drop = 0;\n while (drop < this.frames.length && this.frames[drop].seq <= throughSeq) drop += 1;\n this.removeOldest(drop);\n }\n\n /**\n * The ordered frames with `seq > lastSeq`. Returns an `overrun` marker when `lastSeq` is older than the\n * oldest retained frame (i.e. some frame after `lastSeq` was already evicted) — the client must full-refresh.\n * `lastSeq` at or beyond the newest seq yields an empty tail (nothing to replay).\n */\n public tailAfter(lastSeq: number): TResumeTail {\n if (this.frames.length === 0) {\n // Nothing retained. Only an overrun if the client is behind the frames we HAVE emitted+evicted.\n return lastSeq >= this.nextSeq - 1 ? { kind: 'tail', frames: [] } : { kind: 'overrun' };\n }\n const oldest = this.frames[0].seq;\n if (lastSeq < oldest - 1) return { kind: 'overrun' };\n return { kind: 'tail', frames: this.frames.filter((f) => f.seq > lastSeq) };\n }\n\n /** The last seq assigned (0 when nothing has been appended). */\n public get lastSeq(): number {\n return this.nextSeq - 1;\n }\n\n /** Retained frame count (diagnostics/tests). */\n public get size(): number {\n return this.frames.length;\n }\n\n private evict(): void {\n let drop = 0;\n let remainingBytes = this.bytes;\n // Evict oldest while over the frame cap, or over the byte cap (but always keep at least the newest frame).\n while (\n this.frames.length - drop > this.maxFrames ||\n (remainingBytes > this.maxBytes && this.frames.length - drop > 1)\n ) {\n remainingBytes -= this.sizes[drop];\n drop += 1;\n }\n this.removeOldest(drop);\n }\n\n /** The single place that mutates `frames`/`sizes`/`bytes` for removal, so byte accounting can't drift. */\n private removeOldest(count: number): void {\n if (count <= 0) return;\n for (let i = 0; i < count; i += 1) this.bytes -= this.sizes[i] ?? 0;\n this.frames.splice(0, count);\n this.sizes.splice(0, count);\n if (this.frames.length === 0) this.bytes = 0;\n }\n}\n","/**\n * Payload-agnostic channel frame codec (TRANS-001).\n *\n * The single place that knows the channel envelope. Pure and transport-neutral: a WebSocket binary\n * frame, a WebRTC data-channel message, or any other byte carrier can use it unchanged. It NEVER\n * interprets a binary body — that is the whole point of the payload-agnostic profile.\n *\n * Wire layout (all multi-byte integers big-endian):\n *\n * ```\n * 0..2 magic 'R','B','F' ← lets a carrier route a frame without parsing it\n * 3 version (1)\n * 4 kind: 0x01 = opaque binary, 0x02 = structured event (UTF-8 JSON body)\n * 5 channel-name length in bytes (1..255)\n * 6.. channel name (UTF-8)\n * +4 seq (uint32)\n * ... body — opaque bytes (kind 0x01) or UTF-8 JSON `{ \"event\": string, \"payload\": … }` (0x02)\n * ```\n *\n * The envelope deliberately carries NO length prefix for the body: the carrier already delimits the\n * message (a WebSocket frame is a message boundary), so a length field would only be a second source\n * of truth that can disagree with the first.\n */\n\nimport type {\n IBinaryFrame,\n IChannelEventFrame,\n TChannelReceiveResult,\n} from '@robota-sdk/agent-interface-transport';\n\n/**\n * The JSON-serializable payload type an event frame carries. Read off the contract (rather than\n * re-importing `agent-core`'s `TUniversalValue`) so this package keeps its single contract edge.\n */\ntype TEventPayload = IChannelEventFrame['payload'];\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/** `R`, `B`, `F` — the shared prefix every channel frame starts with. */\nexport const CHANNEL_FRAME_MAGIC: Uint8Array = encoder.encode('RBF');\n\n/** Envelope version. Bumped only on an incompatible layout change. */\nexport const CHANNEL_FRAME_VERSION = 1;\n\nconst KIND_BINARY = 0x01;\nconst KIND_EVENT = 0x02;\n\n/** Bytes before the channel name: magic(3) + version(1) + kind(1) + name length(1). */\nconst PREFIX_BYTES = 6;\n/** Bytes after the channel name and before the body: seq(4). */\nconst SEQ_BYTES = 4;\n\nconst MAX_CHANNEL_NAME_BYTES = 255;\nconst MAX_SEQ = 0xffff_ffff;\n\n/** Validate the envelope fields both encoders share, then return the encoded channel name. */\nfunction encodeChannelName(channel: string, seq: number): Uint8Array {\n const name = encoder.encode(channel);\n if (name.byteLength === 0 || name.byteLength > MAX_CHANNEL_NAME_BYTES) {\n throw new Error(\n `channel name must be 1..${MAX_CHANNEL_NAME_BYTES} UTF-8 bytes (got ${name.byteLength})`,\n );\n }\n if (!Number.isInteger(seq) || seq < 0 || seq > MAX_SEQ) {\n throw new Error(`seq must be an integer in 0..${MAX_SEQ} (got ${seq})`);\n }\n return name;\n}\n\n/** Write the shared header into `out` and return the offset where the body starts. */\nfunction writeHeader(out: Uint8Array, kind: number, name: Uint8Array, seq: number): number {\n out.set(CHANNEL_FRAME_MAGIC, 0);\n out[3] = CHANNEL_FRAME_VERSION;\n out[4] = kind;\n out[5] = name.byteLength;\n out.set(name, PREFIX_BYTES);\n const seqAt = PREFIX_BYTES + name.byteLength;\n new DataView(out.buffer, out.byteOffset, out.byteLength).setUint32(seqAt, seq, false);\n return seqAt + SEQ_BYTES;\n}\n\n/** Encode an opaque binary frame. The payload bytes are copied verbatim. */\nexport function encodeBinaryFrame(frame: IBinaryFrame): Uint8Array {\n const name = encodeChannelName(frame.channel, frame.seq);\n const out = new Uint8Array(PREFIX_BYTES + name.byteLength + SEQ_BYTES + frame.payload.byteLength);\n const bodyAt = writeHeader(out, KIND_BINARY, name, frame.seq);\n out.set(frame.payload, bodyAt);\n return out;\n}\n\n/** Encode a structured, consumer-declared event frame (UTF-8 JSON body). */\nexport function encodeChannelEventFrame(frame: IChannelEventFrame): Uint8Array {\n const name = encodeChannelName(frame.channel, frame.seq);\n const body = encoder.encode(JSON.stringify({ event: frame.event, payload: frame.payload }));\n const out = new Uint8Array(PREFIX_BYTES + name.byteLength + SEQ_BYTES + body.byteLength);\n const bodyAt = writeHeader(out, KIND_EVENT, name, frame.seq);\n out.set(body, bodyAt);\n return out;\n}\n\n/** True when `bytes` starts with the channel-frame magic — a cheap, non-parsing routing check. */\nexport function isChannelFrame(bytes: Uint8Array): boolean {\n if (bytes.byteLength < CHANNEL_FRAME_MAGIC.length) return false;\n return CHANNEL_FRAME_MAGIC.every((b, i) => bytes[i] === b);\n}\n\n/** The decoded shape of an event frame's JSON body. */\ninterface IEventBody {\n readonly event?: TEventPayload;\n readonly payload?: TEventPayload;\n}\n\n/** Parse the JSON body of an event frame into its `event` + `payload` fields, or state why not. */\nfunction parseEventBody(\n body: Uint8Array,\n): { ok: true; event: string; payload: TEventPayload } | { ok: false; error: string } {\n const text = decoder.decode(body);\n let parsed: TEventPayload;\n try {\n parsed = JSON.parse(text) as TEventPayload;\n } catch {\n // An error RESULT, not a fallback: the caller reports it to the peer as a protocol error.\n return { ok: false, error: 'channel frame: malformed JSON body' };\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return { ok: false, error: 'channel frame: event body must be a JSON object' };\n }\n const { event, payload } = parsed as IEventBody;\n if (typeof event !== 'string' || event.length === 0) {\n return { ok: false, error: 'channel frame: event body is missing a non-empty `event` name' };\n }\n return { ok: true, event, payload: payload ?? null };\n}\n\n/**\n * Decode a channel frame. Returns an explicit result union — a non-channel, truncated, or malformed\n * input is a stated error the carrier reports to the peer, never a thrown exception on the hot path\n * and never a silently dropped frame.\n */\nexport function decodeChannelFrame(bytes: Uint8Array): TChannelReceiveResult {\n if (!isChannelFrame(bytes)) {\n return { ok: false, error: 'channel frame: bad magic (not a channel frame)' };\n }\n if (bytes.byteLength < PREFIX_BYTES) {\n return { ok: false, error: 'channel frame: truncated header' };\n }\n if (bytes[3] !== CHANNEL_FRAME_VERSION) {\n return { ok: false, error: `channel frame: unsupported version ${String(bytes[3])}` };\n }\n\n const kind = bytes[4];\n const nameLength = bytes[5] ?? 0;\n if (nameLength === 0) return { ok: false, error: 'channel frame: empty channel name' };\n\n const seqAt = PREFIX_BYTES + nameLength;\n const bodyAt = seqAt + SEQ_BYTES;\n if (bytes.byteLength < bodyAt) {\n return { ok: false, error: 'channel frame: truncated (header longer than the frame)' };\n }\n\n const channel = decoder.decode(bytes.subarray(PREFIX_BYTES, seqAt));\n const seq = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(\n seqAt,\n false,\n );\n const body = bytes.subarray(bodyAt);\n\n if (kind === KIND_BINARY) {\n // Copy so the frame does not alias the carrier's (possibly pooled/reused) receive buffer.\n return { ok: true, frame: { kind: 'binary', channel, seq, payload: Uint8Array.from(body) } };\n }\n if (kind === KIND_EVENT) {\n const parsed = parseEventBody(body);\n if (!parsed.ok) return { ok: false, error: parsed.error };\n return {\n ok: true,\n frame: { kind: 'event', channel, seq, event: parsed.event, payload: parsed.payload },\n };\n }\n return { ok: false, error: `channel frame: unknown kind ${String(kind)}` };\n}\n","/**\n * Persistent session-resume bridge for the reconnectable WebRTC path (REMOTE-013 Stage E4).\n *\n * Created ONCE per remote session and OUTLIVES individual data channels. It subscribes to the session's\n * events exactly once, stamps every outbound `TServerMessage` with a **monotonic `seq` that is continuous\n * across channel drops**, and retains the un-acked tail in a {@link ResumeBuffer}. The data channel is a\n * SWAPPABLE sink:\n *\n * - `attach(sink)` — set the current sink (a new channel after a reconnect). Does NOT auto-replay; replay is\n * driven by the client's `resume`.\n * - `detach()` — clear the sink but KEEP buffering, so output produced while no channel is attached (the gap)\n * is captured and replayable.\n * - `onClientMessage(data)` — `resume{lastSeq}` replays the buffered tail (or `resume_gap` on overrun); `ack{seq}`\n * frees the buffer; everything else routes to the session (reusing the shared `handleClientMessage`).\n * - `dispose()` — unsubscribe (host reconnect-window ceiling / teardown).\n *\n * This is the fix for the placement defect: because the seq counter + buffer live here (not in the per-channel\n * `createSessionMessageHandler`), `seq` does not reset when a reconnect builds a new channel, and gap output is not lost.\n * The WS localhost path does NOT use this bridge — it keeps `createSessionMessageHandler` unchanged.\n */\n\nimport { createOutboundDelivery } from './outbound-delivery.js';\nimport { ResumeBuffer, type IResumeBufferOptions } from './resume-buffer.js';\nimport { subscribeSessionEvents } from './session-events.js';\nimport { handleClientMessage, parseClientMessage } from './session-message-handler.js';\n\nimport type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { IProtocolSession } from './protocol-session.js';\nimport type { IUsageQueryReporters } from './usage-messages.js';\nimport type { TSeqServerMessage, TServerMessage } from './wire-messages.js';\nimport type { TCommandSurfaceLocality } from '@robota-sdk/agent-interface-command';\nimport type { TUsageSurface } from '@robota-sdk/agent-interface-analytics';\nimport type { TDriverId } from '@robota-sdk/agent-interface-session';\n\n/** The current channel sink — receives a serialized JSON frame to put on the wire. */\nexport type TResumeSink = (data: string) => void;\n\n/** Options for {@link SessionResumeBridge.attach}. */\nexport interface IAttachOptions {\n /**\n * REMOTE-013 E4: on a RECONNECT, HOLD live sink-forwarding until the client's `resume` has replayed the\n * buffered tail — otherwise a live frame emitted between attach and the inbound `resume` would leapfrog the\n * gap frames on the wire, and the client's seq-dedup would then discard the (older, lower-seq) gap. Every\n * live event is already appended to the buffer, so holding loses nothing; `replay()` flushes in order and\n * un-holds. First-pair attach does NOT hold (the client starts at seq 0, frames arrive in order).\n */\n readonly awaitResume?: boolean;\n /** Sink/carrier-owned failure lifecycle for this attachment only. */\n readonly onDeliveryError: (error: Error, event: string) => void;\n /**\n * ARCH-030: this attachment's carrier backpressure reading, if it has one.\n *\n * Threaded through because the replay path is the burst this budget exists for — a reconnecting\n * peer is handed the whole retained tail at once, and without a reading the boundary here would\n * have no budget at all while the per-channel boundary did.\n */\n readonly pendingBytes?: () => number | undefined;\n}\n\nexport interface ISessionResumeBridgeOptions {\n readonly session: IProtocolSession;\n readonly buffer?: IResumeBufferOptions;\n /** REMOTE-014 E5: the SERVER-ASSIGNED driver id for this surface, injected into inbound submit/command/prompt-response. */\n readonly driverId?: TDriverId;\n readonly surface?: TUsageSurface;\n readonly personalUsageReporter?: IUsageQueryReporters['personalUsageReporter'];\n readonly usageReporter?: IUsageQueryReporters['usageReporter'];\n readonly storedSessionUsageReporter?: IUsageQueryReporters['storedSessionUsageReporter'];\n /** #3282 §4 part b-2: this bridge is the reconnectable WebRTC path, so it is always `'remote'`. */\n readonly commandSurfaceLocality?: TCommandSurfaceLocality;\n /** See {@link ISessionMessageHandlerOptions.maxFrameBytes}: the channel's limit, not the inbound one. */\n readonly maxFrameBytes?: number;\n}\n\nexport class SessionResumeBridge {\n private readonly session: IProtocolSession;\n private readonly buffer: ResumeBuffer;\n private driverId?: TDriverId;\n private readonly reporters: IUsageQueryReporters;\n private readonly surface?: TUsageSurface;\n private readonly commandSurfaceLocality?: TCommandSurfaceLocality;\n private readonly maxFrameBytes?: number;\n /**\n * Ends with the bridge, not with a channel: a channel drop keeps the session reconnectable, so work\n * started for a hosted app view (a call waiting on its prompt) survives it and is cancelled only when\n * the host gives up on the session.\n */\n private readonly lifetime = new AbortController();\n private readonly unsubscribe: () => void;\n private onDeliveryError?: IAttachOptions['onDeliveryError'];\n /**\n * ARCH-030: the attachment's outbound boundary — and the only reference this class keeps to the current\n * channel, which is why `dispose()` goes through `detach()` rather than clearing a separate field.\n * Built per `attach` because a boundary LATCHES on its first failure, so a fresh one is exactly what\n * un-latches the session for the next channel. It holds the only try/catch on the frame path.\n */\n private outbound?: TOutboundDeliver;\n /**\n * The session-lifetime entry every frame takes — event fan-out AND every reply to an inbound frame —\n * before it is seq-stamped and buffered by {@link emit}. Distinct from {@link outbound}, which is the\n * per-attachment exit to the current channel; this one outlives channels because the subscription does.\n *\n * It guards the BUFFERING step, not a carrier, and it latches for the life of the session on purpose:\n * the failure it is placed against is the buffer refusing a frame on capacity, and a resume buffer that\n * cannot accept frames cannot honour a later `resume` either, so there is nothing a reconnect recovers.\n * That is the opposite of {@link outbound}, where the next channel IS the recovery — the two scopes\n * differ because the failures do. (A per-frame serialization failure would leave the buffer intact, but\n * such a frame is undeliverable on every carrier, since each one stringifies it too; it is reported and\n * the carrier torn down, never silently dropped.)\n */\n private readonly emitBoundary: TOutboundDeliver;\n private disposed = false;\n /** REMOTE-013 E4: while true, live emits are buffered but NOT forwarded — released by `replay()` on reconnect. */\n private holding = false;\n\n public constructor(options: ISessionResumeBridgeOptions) {\n this.session = options.session;\n this.buffer = new ResumeBuffer(options.buffer);\n this.driverId = options.driverId;\n this.surface = options.surface;\n this.commandSurfaceLocality = options.commandSurfaceLocality;\n this.maxFrameBytes = options.maxFrameBytes;\n this.reporters = {\n personalUsageReporter: options.personalUsageReporter,\n usageReporter: options.usageReporter,\n storedSessionUsageReporter: options.storedSessionUsageReporter,\n };\n // ONE subscription for the whole session — outlives every channel. Every event → seq-stamped + buffered.\n // CMD-004 Stage D: `ui_intent` is requester-routed against the LATE-BOUND driver id (bound by\n // `setDriverId` after pairing) — routing happens BEFORE buffering, so a foreign surface's intent\n // consumes no seq and can never leak through a later `resume` replay.\n //\n // ARCH-030: built through the factory, never cast — the brand is only worth having if this class\n // does not spell its way around it. `emit` buffers and then hands the stamped frame to whatever\n // boundary is currently ATTACHED, so it does not itself throw on a carrier failure; this boundary\n // exists so that if it ever did, the failure would be reported rather than escaping the session's\n // event listener.\n this.emitBoundary = createOutboundDelivery(\n (message) => this.emit(message),\n (error, event) => this.reportDeliveryError(error, event),\n );\n this.unsubscribe = subscribeSessionEvents(this.session, this.emitBoundary, {\n getSurfaceDriverId: () => this.driverId,\n });\n }\n\n /** Set the current channel sink (on connect / reconnect). Live messages reach the client; replay is `resume`-driven. */\n public attach(sink: TResumeSink, options: IAttachOptions): void {\n if (this.disposed) return;\n this.onDeliveryError = options.onDeliveryError;\n this.outbound = createOutboundDelivery(\n (message) => sink(JSON.stringify(message)),\n (error, event) => this.reportDeliveryError(error, event),\n options.pendingBytes,\n );\n // On a reconnect, hold live forwarding until `resume` flushes the buffered tail (avoids the live-vs-replay race).\n this.holding = options.awaitResume === true;\n }\n\n /** Clear the sink (channel drop). Buffering continues so gap output is retained for the next `resume`. */\n public detach(): void {\n this.outbound = undefined;\n this.onDeliveryError = undefined;\n }\n\n /**\n * REMOTE-014 E5: bind the SERVER-ASSIGNED driver id (the E3 `deviceId`) once the peer pairs — the bridge\n * injects it into every subsequent inbound submit/command/prompt-response for co-drive attribution.\n */\n public setDriverId(driverId: TDriverId): void {\n this.driverId = driverId;\n }\n\n /** Route one inbound channel frame: `resume`/`ack` handled here; everything else → the session. */\n public onClientMessage(data: string): void {\n if (this.disposed) return;\n const msg = parseClientMessage(data, this.emitBoundary);\n if (!msg) return;\n if (msg.type === 'resume') {\n this.replay(msg.lastSeq);\n return;\n }\n if (msg.type === 'ack') {\n this.buffer.ackThrough(msg.seq);\n return;\n }\n // Session control/query/background/prompt-response — responses funnel back through `emit` (seq'd + buffered).\n // REMOTE-014 E5: inject the server-assigned driver id (submit/prompt-response attribution).\n handleClientMessage(\n this.session,\n this.emitBoundary,\n msg,\n this.driverId,\n this.reporters,\n this.surface,\n undefined,\n undefined,\n this.commandSurfaceLocality,\n {\n signal: this.lifetime.signal,\n ...(this.maxFrameBytes !== undefined ? { maxFrameBytes: this.maxFrameBytes } : {}),\n },\n );\n }\n\n /** Unsubscribe from the session (host reconnect-window ceiling / teardown). Idempotent. */\n public dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.lifetime.abort();\n // Through `detach`, not a field assignment: the boundary's closure is what holds the channel, so\n // clearing anything else released nothing and left teardown documented but not performed.\n this.detach();\n this.unsubscribe();\n }\n\n /** Diagnostics/tests: the last seq assigned. */\n public get lastSeq(): number {\n return this.buffer.lastSeq;\n }\n\n /**\n * Stamp a seq, retain in the buffer, and forward to the current sink — UNLESS holding for a reconnect replay.\n *\n * ARCH-030: the `buffer.append` happens BEFORE the boundary, deliberately. The boundary latches on its\n * first failure and drops everything after it, so a frame appended afterwards would be lost — appending\n * first means a dropped frame is still in the un-acked tail and still replays on the next `resume`.\n */\n private emit(message: TServerMessage): void {\n if (this.disposed) return;\n const seq = this.buffer.append(message);\n if (this.holding) return; // buffered only; `replay()` will flush it in order and release the hold\n this.deliverFrame({ ...message, seq } as TSeqServerMessage);\n }\n\n /**\n * Replay the buffered tail after `lastSeq` (or `resume_gap` on overrun), then RELEASE the reconnect hold so\n * subsequent live frames flow in order behind the flushed gap.\n *\n * A failing sink reports ONCE across the whole tail: the boundary latches on the first failure and every\n * remaining frame is dropped silently. Those frames stay un-acked in the buffer and replay on the next\n * attachment. Before ARCH-030 the single report was a side effect of `detach()` running mid-loop; now it\n * is the boundary's stated contract.\n */\n private replay(lastSeq: number): void {\n const tail = this.buffer.tailAfter(lastSeq);\n if (tail.kind === 'overrun') {\n // `resume_gap` is neither stamped nor buffered: the client answers it with a full `get-messages`\n // refresh, so there is nothing for it to resume from.\n this.deliverFrame({ type: 'resume_gap' });\n this.holding = false; // client will full-refresh via get-messages; let live frames flow\n return;\n }\n for (const frame of tail.frames) {\n this.deliverFrame({ ...frame.message, seq: frame.seq } as TSeqServerMessage);\n }\n this.holding = false; // gap flushed in order — resume live forwarding\n }\n\n /** The ONE outbound exit from this class. No frame reaches the sink except through the attachment's boundary. */\n private deliverFrame(message: TServerMessage): void {\n this.outbound?.(message);\n }\n\n /** Detach the failed sink but retain the frame, then notify its attachment owner. */\n private reportDeliveryError(error: Error, event: string): void {\n const onDeliveryError = this.onDeliveryError;\n this.detach();\n try {\n onDeliveryError?.(error, event);\n } catch {\n // Carrier diagnostics cannot corrupt the retained buffer or the committed operation.\n }\n }\n}\n","/**\n * PEER-001 (#1809): the receiver's record of what it has already taken responsibility for.\n *\n * ## What this decides, and why it is not the carrier's job\n *\n * The issue requires delivery, acknowledgement, duplicate, retry and shutdown to produce\n * \"deterministic, documented outcomes\". Every one of those is a question about what the receiver has\n * SEEN BEFORE, which no carrier can answer: a WebRTC data channel redelivers on reconnect, a retry\n * repeats a message the sender already sent, and neither the socket nor the frame codec has any\n * memory to consult. So the decision lives here, once, and the carriers stay dumb.\n *\n * ## The rules, stated rather than emergent\n *\n * - **A repeated `id` is a duplicate**, and the ledger returns the SAME ack it returned the first\n * time. That is what makes a retry safe: the sender learns the message landed, and the receiver\n * does not deliver it twice. An ack that merely said \"duplicate\" without the original state would\n * force the sender to guess whether the first attempt was accepted or refused.\n * - **Sequence is per origin.** Two peers are independent senders; a shared counter would make one\n * peer's traffic look like the other's gap.\n * - **A gap is REPORTED, never silently reordered.** If sequence 5 arrives while 4 has not, the\n * ledger says so. Buffering-and-reordering is a policy the session layer may choose; inventing it\n * here would hide a lost message behind an apparent success, which is the failure mode this whole\n * contract exists to avoid.\n * - **Nothing is remembered across shutdown.** The ledger is per-connection state by construction —\n * a peer that reconnects gets a fresh sequence space, and the session layer decides what to do\n * with a peer that restarts mid-conversation.\n */\n\nimport type {\n IPeerMessage,\n IPeerMessageAck,\n TPeerDeliveryState,\n} from '@robota-sdk/agent-interface-session-mobility';\n\n/**\n * What the receiver has seen from one origin.\n *\n * `delivered` is a SET, not just a high-water mark, and that distinction is the whole of review\n * finding #1. A high-water mark cannot tell \"sequence 2 was already used\" from \"sequence 2 is the\n * gap we reported and it has just arrived\" — and answering the second as if it were the first\n * refuses a message that was never delivered, permanently. `highestSequence` is kept alongside it\n * because reporting a gap needs the frontier, not the membership.\n *\n * Exported because it is reachable from `IPeerMessageLedger`. A public interface whose field type is\n * unexported is a name a consumer cannot write down (review finding #2). Measured: `tsdown` inlines\n * it into the bundled declaration so no TS4033 is raised today — the finding's build-breakage claim\n * does not reproduce — but a type nobody can name is still a bad public surface.\n */\nexport interface IOriginRecord {\n /** The highest sequence delivered from this origin — the frontier a gap is measured against. */\n highestSequence: number;\n /** Every sequence actually delivered. Membership, not a range: gaps get filled out of order. */\n delivered: Set<number>;\n /** Acks already issued, by message id, so a retry gets the original answer back. */\n issued: Map<string, IPeerMessageAck>;\n}\n\n/** Per-connection receive state. Created empty; discarded when the connection ends. */\nexport interface IPeerMessageLedger {\n readonly origins: Map<string, IOriginRecord>;\n}\n\nexport function createPeerMessageLedger(): IPeerMessageLedger {\n return { origins: new Map() };\n}\n\n/** Why a message was not accepted, when it was not. */\nexport interface IPeerMessageRejection {\n readonly state: Extract<TPeerDeliveryState, 'refused' | 'failed'>;\n readonly reason: string;\n}\n\nexport interface IPeerMessageVerdict {\n readonly ack: IPeerMessageAck;\n /**\n * Should the session deliver this to the agent?\n *\n * False for a duplicate and for a refusal. Separate from `ack.state` because a caller that\n * re-derived it from the state string would have to re-list which states mean \"deliver\", and that\n * list is exactly what drifts.\n */\n readonly deliver: boolean;\n /**\n * Set when this message's sequence is ahead of the last one delivered from its origin.\n *\n * Reported, not resolved. The session layer decides whether a gap is worth waiting for; the\n * ledger's job is to make sure it is never invisible.\n */\n readonly gapBefore?: number;\n}\n\n/**\n * Record a message and say what should happen to it.\n *\n * Pure with respect to its inputs except for the ledger it is handed, which it updates in place —\n * the caller owns the ledger's lifetime, so a connection ending is a reference going away rather\n * than a cleanup step someone can forget.\n */\nexport function admitPeerMessage(\n ledger: IPeerMessageLedger,\n message: IPeerMessage,\n rejection?: IPeerMessageRejection,\n): IPeerMessageVerdict {\n const originId = message.origin.sessionId;\n let record = ledger.origins.get(originId);\n if (record === undefined) {\n record = { highestSequence: 0, delivered: new Set(), issued: new Map() };\n ledger.origins.set(originId, record);\n }\n\n // A repeated id is answered with the ORIGINAL verdict, before anything else is considered: a\n // retry must not be re-judged, or a message accepted once could be refused on its second arrival\n // and the sender would have two contradictory answers for one message.\n const previous = record.issued.get(message.id);\n if (previous !== undefined) {\n return { ack: { ...previous, state: 'duplicate' }, deliver: false };\n }\n\n if (rejection !== undefined) {\n const ack: IPeerMessageAck = {\n id: message.id,\n sequence: message.sequence,\n state: rejection.state,\n reason: rejection.reason,\n };\n record.issued.set(message.id, ack);\n return { ack, deliver: false };\n }\n\n // A sequence this origin ACTUALLY delivered before, under a different id: a sender reusing a\n // sequence, which is a protocol error rather than a duplicate.\n //\n // Membership, not `<= highestSequence`. The high-water form refused the late arrival of a gap it\n // had itself reported — deliver 1, receive 3 (gap at 2 reported), then 2 arrives and is rejected\n // as \"already used\" although it was never delivered. That contradicted this module's own stated\n // rule that a gap is reported so the SESSION layer can decide, since the session never got the\n // chance. Found in review.\n if (record.delivered.has(message.sequence)) {\n const ack: IPeerMessageAck = {\n id: message.id,\n sequence: message.sequence,\n state: 'refused',\n reason:\n `sequence ${message.sequence} was already delivered by this origin under a different ` +\n 'message id. A retry must repeat its id; a new id on a delivered sequence cannot be ' +\n 'ordered against what the receiver already has.',\n };\n record.issued.set(message.id, ack);\n return { ack, deliver: false };\n }\n\n const expected = record.highestSequence + 1;\n const gapBefore = message.sequence > expected ? expected : undefined;\n\n const ack: IPeerMessageAck = {\n id: message.id,\n sequence: message.sequence,\n state: 'delivered',\n };\n record.issued.set(message.id, ack);\n record.delivered.add(message.sequence);\n // The frontier only moves FORWARD. A gap arriving late fills membership without rewinding what a\n // later gap is measured against.\n record.highestSequence = Math.max(record.highestSequence, message.sequence);\n\n return gapBefore === undefined ? { ack, deliver: true } : { ack, deliver: true, gapBefore };\n}\n\n/**\n * Mark a delivered message as acknowledged by the agent side.\n *\n * Separate from `admitPeerMessage` because delivery and acknowledgement answer different questions —\n * \"did the receiver take it\" versus \"did the agent act on it\" — and a sender waiting for the second\n * must not be told the first.\n */\nexport function acknowledgePeerMessage(\n ledger: IPeerMessageLedger,\n originSessionId: string,\n id: string,\n): IPeerMessageAck | undefined {\n const record = ledger.origins.get(originSessionId);\n const issued = record?.issued.get(id);\n if (record === undefined || issued === undefined || issued.state !== 'delivered')\n return undefined;\n const acknowledged: IPeerMessageAck = { ...issued, state: 'acknowledged' };\n record.issued.set(id, acknowledged);\n return acknowledged;\n}\n\n/** Forget one origin — used when a peer disconnects and its sequence space is no longer meaningful. */\nexport function forgetPeerOrigin(ledger: IPeerMessageLedger, originSessionId: string): void {\n ledger.origins.delete(originSessionId);\n}\n","/**\n * The one argument a person recognises a tool call by, for every transport that puts a permission\n * request to a person in its own wire form. Shared so the transports name the same call the same\n * way; the built-in file tools take `filePath`, other tools `file_path` or `path`.\n */\nimport type { TToolArgs } from '@robota-sdk/agent-core';\n\n/** In the order the tools put them first. */\nconst SUMMARY_ARGUMENTS = ['command', 'filePath', 'file_path', 'path', 'url', 'pattern', 'query'];\n\n/** The call's identifying argument, or `undefined` when it has none worth showing. */\nexport function permissionSummaryArgument(toolArgs: TToolArgs): string | undefined {\n for (const key of SUMMARY_ARGUMENTS) {\n const value = toolArgs[key];\n if (typeof value === 'string' && value !== '') return value;\n }\n return undefined;\n}\n","/**\n * HANDOFF-001 (#1811): moving a session record that does not fit in one frame.\n *\n * A data channel has a maximum message size; a session record is a whole conversation. So the\n * payload travels in pieces, and this owns the two halves of that: cutting it up, and putting it\n * back together in the face of retries, reordering and a peer that says something inconsistent.\n *\n * ## Why the cut is made on BYTES rather than on the string\n *\n * The channel's limit is in bytes, and the serialized record is a JavaScript string — UTF-16 code\n * units, where one character can be one byte or four. Slicing by characters and hoping the pieces\n * fit means either measuring each slice's encoded length anyway, or shipping frames that overflow\n * the channel for anyone whose conversation contains an emoji or a non-Latin script.\n *\n * Cutting the encoded bytes makes the budget exact. It costs base64's third: a byte slice is not a\n * valid string on its own — it can end mid-character — so it cannot ride in a JSON frame as text.\n * That cost is deliberate and is the smaller risk. The alternative works only because\n * `JSON.stringify` has escaped lone surrogates since ES2019; a transfer whose correctness rests on\n * that, and which fails as silent corruption when something in the chain does not, is not a trade\n * worth a third of the bandwidth.\n *\n * ## What this does NOT decide\n *\n * It does not verify the digest. `verifyHandoffPayload` owns that, and asking the same question in\n * two places is how the two answers drift. This reports that all the pieces arrived; whether they\n * are the RIGHT pieces is the next check, and it deliberately runs on the whole reassembled payload\n * rather than per chunk — a per-chunk digest would say each piece is intact while the set is not the\n * set that was sent.\n */\n\nimport type { IHandoffIntegrity } from '@robota-sdk/agent-interface-session-mobility';\n\n/**\n * One piece of a payload in flight.\n *\n * `total` travels on every chunk rather than in a separate header frame: a header can be the frame\n * that gets lost, and a receiver that learned the count from a message it never got would wait\n * forever for a number it cannot ask for again.\n */\nexport interface IHandoffChunk {\n readonly handoffId: string;\n /** 0-based position in the sequence. */\n readonly index: number;\n /** How many chunks make up the whole payload. */\n readonly total: number;\n /** base64 of this slice of the UTF-8 encoded payload. */\n readonly data: string;\n}\n\n/**\n * Default frame budget, in bytes of encoded chunk data.\n *\n * 16 KiB because that is the size every SCTP implementation carries without negotiation. Larger\n * works between two peers that both support it and fails between two that do not — and it fails at\n * transfer time, on the user's session, not in a test.\n */\nexport const DEFAULT_MAX_CHUNK_BYTES = 16 * 1024;\n\n// This module is exported from the package root, which also builds for the browser, so it uses the\n// web platform's bytes, text codecs and base64 (`btoa`/`atob`) rather than Node's `Buffer`.\n\n/** How many bytes go through `String.fromCharCode` at once, well under the argument-count limit. */\nconst BINARY_STRING_STEP = 0x8000;\n\nfunction toBase64(bytes: Uint8Array): string {\n let binary = '';\n for (let offset = 0; offset < bytes.length; offset += BINARY_STRING_STEP) {\n binary += String.fromCharCode(...bytes.subarray(offset, offset + BINARY_STRING_STEP));\n }\n return btoa(binary);\n}\n\n/** The bytes `data` encodes, or `undefined` when it is not base64 at all. */\nfunction fromBase64(data: string): Uint8Array | undefined {\n let binary: string;\n try {\n binary = atob(data);\n } catch {\n return undefined;\n }\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\nfunction sameBytes(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i += 1) if (a[i] !== b[i]) return false;\n return true;\n}\n\n/**\n * Cut a sealed payload into chunks.\n *\n * Takes the serialized string the seal produced, not the record — the digest is over those exact\n * bytes, and re-serializing here would let a chunked payload differ from the one the manifest\n * describes.\n */\nexport function chunkHandoffPayload(\n handoffId: string,\n serialized: string,\n maxChunkBytes: number = DEFAULT_MAX_CHUNK_BYTES,\n): readonly IHandoffChunk[] {\n if (!Number.isInteger(maxChunkBytes) || maxChunkBytes < 1) {\n throw new Error(\n `handoff chunking: maxChunkBytes must be a positive integer, got ${maxChunkBytes}. ` +\n 'A zero or negative budget produces an infinite sequence of empty chunks, which presents ' +\n 'as a hang rather than as the configuration error it is.',\n );\n }\n const bytes = new TextEncoder().encode(serialized);\n // An empty payload is still one chunk. Zero chunks would leave the receiver unable to tell a\n // completed empty transfer from one that never started.\n const total = Math.max(1, Math.ceil(bytes.byteLength / maxChunkBytes));\n const chunks: IHandoffChunk[] = [];\n for (let index = 0; index < total; index += 1) {\n const slice = bytes.subarray(index * maxChunkBytes, (index + 1) * maxChunkBytes);\n chunks.push({ handoffId, index, total, data: toBase64(slice) });\n }\n return chunks;\n}\n\n/** Why a chunk was not accepted. */\nexport type TChunkRejection =\n /** Belongs to a different transfer. */\n | 'wrong-handoff'\n /** `index` is negative, or beyond the declared `total`. */\n | 'out-of-range'\n /** Its `total` disagrees with what earlier chunks declared. */\n | 'inconsistent-total'\n /** Its `data` is not decodable base64. */\n | 'undecodable';\n\nexport type TChunkOutcome =\n /** Stored; more are still needed. */\n | 'accepted'\n /** Already had this index, and it matched. A retry, not a problem. */\n | 'duplicate'\n /** Stored, and that was the last one missing. */\n | 'complete'\n | 'refused';\n\nexport interface IChunkResult {\n readonly outcome: TChunkOutcome;\n readonly rejection?: TChunkRejection;\n /** Present when complete: the reassembled payload, ready for the integrity check. */\n readonly serialized?: string;\n /** How many distinct chunks are held. */\n readonly received: number;\n /** How many are expected, once anything has declared it. */\n readonly expected?: number;\n}\n\n/**\n * Collects the chunks of ONE transfer.\n *\n * One assembler per `handoffId`, because a shared one would have to key everything by transfer and\n * would then be a place where two transfers can be confused for each other. A chunk naming a\n * different transfer is refused rather than routed — routing is the caller's job and doing it here\n * would make this a dispatcher as well as a buffer.\n */\nexport class HandoffChunkAssembler {\n private readonly chunks = new Map<number, Uint8Array>();\n private declaredTotal: number | undefined;\n\n constructor(private readonly handoffId: string) {}\n\n get received(): number {\n return this.chunks.size;\n }\n\n get expected(): number | undefined {\n return this.declaredTotal;\n }\n\n /** Take one chunk. Reordering and retries are normal; inconsistency is not. */\n accept(chunk: IHandoffChunk): IChunkResult {\n const refuse = (rejection: TChunkRejection): IChunkResult => ({\n outcome: 'refused',\n rejection,\n received: this.chunks.size,\n ...(this.declaredTotal !== undefined ? { expected: this.declaredTotal } : {}),\n });\n\n if (chunk.handoffId !== this.handoffId) return refuse('wrong-handoff');\n if (!Number.isInteger(chunk.total) || chunk.total < 1) return refuse('inconsistent-total');\n if (this.declaredTotal !== undefined && chunk.total !== this.declaredTotal) {\n // A peer that changes its mind about the size has either restarted the transfer under the\n // same id or is not the peer we started with. Either way the pieces held so far can no longer\n // be assumed to belong together.\n return refuse('inconsistent-total');\n }\n if (!Number.isInteger(chunk.index) || chunk.index < 0 || chunk.index >= chunk.total) {\n return refuse('out-of-range');\n }\n\n const decoded = fromBase64(chunk.data);\n // Base64 decoding is lenient — it skips whitespace and accepts non-canonical padding — so a\n // mangled chunk could silently become a different one and corrupt the payload in a way only the\n // final digest would catch, after the whole transfer completed. Re-encoding is how the leniency\n // is detected here instead.\n if (decoded === undefined || toBase64(decoded) !== chunk.data) return refuse('undecodable');\n\n this.declaredTotal = chunk.total;\n const held = this.chunks.get(chunk.index);\n if (held !== undefined) {\n // A retry re-sending the same bytes is normal. Different bytes for the same index is the\n // inconsistent case again, and the same reasoning applies.\n if (sameBytes(held, decoded)) {\n return { outcome: 'duplicate', received: this.chunks.size, expected: chunk.total };\n }\n return refuse('inconsistent-total');\n }\n\n this.chunks.set(chunk.index, decoded);\n if (this.chunks.size < chunk.total) {\n return { outcome: 'accepted', received: this.chunks.size, expected: chunk.total };\n }\n return {\n outcome: 'complete',\n serialized: this.reassemble(chunk.total),\n received: this.chunks.size,\n expected: chunk.total,\n };\n }\n\n /** Which indices are still missing, so a caller can ask for them rather than restarting. */\n missing(): readonly number[] {\n if (this.declaredTotal === undefined) return [];\n const gaps: number[] = [];\n for (let i = 0; i < this.declaredTotal; i += 1) if (!this.chunks.has(i)) gaps.push(i);\n return gaps;\n }\n\n /** Drop everything held. The transfer was abandoned, or its integrity check failed. */\n discard(): void {\n this.chunks.clear();\n this.declaredTotal = undefined;\n }\n\n private reassemble(total: number): string {\n const ordered: Uint8Array[] = [];\n for (let i = 0; i < total; i += 1) {\n const part = this.chunks.get(i);\n if (part === undefined) {\n // Unreachable: size equalled total and every index was range-checked on the way in. Stated\n // as an error rather than a non-null assertion, because the assertion would be the thing\n // that hid it if the invariant ever broke.\n throw new Error(`handoff chunking: chunk ${i} of ${total} is missing at reassembly.`);\n }\n ordered.push(part);\n }\n const joined = new Uint8Array(ordered.reduce((size, part) => size + part.length, 0));\n let offset = 0;\n for (const part of ordered) {\n joined.set(part, offset);\n offset += part.length;\n }\n // Keep a leading byte-order mark: it is part of the payload, as it was with `Buffer`.\n return new TextDecoder('utf-8', { ignoreBOM: true }).decode(joined);\n }\n}\n\n/**\n * How many chunks a payload of this size will take, without building them.\n *\n * For a progress indicator at the sending end, and for a receiver deciding whether it is willing to\n * hold that much before the first chunk arrives.\n */\nexport function chunkCountFor(\n integrity: IHandoffIntegrity,\n maxChunkBytes: number = DEFAULT_MAX_CHUNK_BYTES,\n): number {\n return Math.max(1, Math.ceil(integrity.byteLength / maxChunkBytes));\n}\n"],"mappings":"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"}