@robota-sdk/agent-transport 3.0.0-beta.78 → 3.0.0-beta.81

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 (111) hide show
  1. package/README.md +84 -26
  2. package/dist/browser/client.d.ts +2 -0
  3. package/dist/browser/client.js +1 -0
  4. package/dist/browser/index.d.ts +520 -0
  5. package/dist/browser/index.d.ts.map +1 -0
  6. package/dist/browser/index.js +2 -0
  7. package/dist/browser/index.js.map +1 -0
  8. package/dist/browser/message-decoders-BB8n-H2X.d.ts +252 -0
  9. package/dist/browser/message-decoders-BB8n-H2X.d.ts.map +1 -0
  10. package/dist/browser/message-decoders-CDmDoZPR.js +2 -0
  11. package/dist/browser/message-decoders-CDmDoZPR.js.map +1 -0
  12. package/dist/node/index.cjs +1 -1
  13. package/dist/node/index.d.cts +766 -0
  14. package/dist/node/index.d.cts.map +1 -0
  15. package/dist/node/index.d.ts +765 -27
  16. package/dist/node/index.d.ts.map +1 -1
  17. package/dist/node/index.js +1 -1
  18. package/dist/node/index.js.map +1 -1
  19. package/dist/node/node.cjs +1 -0
  20. package/dist/node/node.d.cts +109 -0
  21. package/dist/node/node.d.cts.map +1 -0
  22. package/dist/node/node.d.ts +109 -0
  23. package/dist/node/node.d.ts.map +1 -0
  24. package/dist/node/node.js +2 -0
  25. package/dist/node/node.js.map +1 -0
  26. package/package.json +68 -47
  27. package/src/__tests__/access-token-verifier.test.ts +672 -0
  28. package/src/__tests__/admission.test.ts +130 -0
  29. package/src/__tests__/channel-frames.test.ts +214 -0
  30. package/src/__tests__/driver-attribution-capability.test.ts +83 -0
  31. package/src/__tests__/handoff-chunking.test.ts +215 -0
  32. package/src/__tests__/handoff-manifest.test.ts +52 -0
  33. package/src/__tests__/message-decoders.test.ts +249 -0
  34. package/src/__tests__/outbound-delivery.test.ts +558 -0
  35. package/src/__tests__/peer-message-ledger.test.ts +200 -0
  36. package/src/__tests__/personal-usage-report.test.ts +193 -0
  37. package/src/__tests__/resume-buffer.test.ts +93 -0
  38. package/src/__tests__/session-event-delivery.test.ts +101 -0
  39. package/src/__tests__/session-message-handler-e5.test.ts +99 -0
  40. package/src/__tests__/session-message-handler.test.ts +590 -0
  41. package/src/__tests__/session-resume-bridge.test.ts +406 -0
  42. package/src/__tests__/submit-prompt-shape.test.ts +78 -0
  43. package/src/__tests__/usage-report-carrier.test.ts +98 -0
  44. package/src/__tests__/ws-broadcast-events.test.ts +84 -0
  45. package/src/background-messages.ts +169 -0
  46. package/src/channel-frames.ts +182 -0
  47. package/src/client.ts +9 -0
  48. package/src/handoff-chunking.ts +235 -0
  49. package/src/index.ts +112 -3
  50. package/src/message-decoders.ts +229 -0
  51. package/src/message-parser.ts +33 -0
  52. package/src/node/access-token-verifier.ts +363 -0
  53. package/src/node/admission.ts +153 -0
  54. package/src/node/handoff-manifest.ts +77 -0
  55. package/src/node/index.ts +10 -0
  56. package/src/outbound-delivery.ts +231 -0
  57. package/src/peer-message-ledger.ts +193 -0
  58. package/src/protocol-session.ts +28 -0
  59. package/src/resume-buffer.ts +123 -0
  60. package/src/session-events.ts +198 -0
  61. package/src/session-message-handler.ts +282 -0
  62. package/src/session-query-messages.ts +42 -0
  63. package/src/session-resume-bridge.ts +252 -0
  64. package/src/usage-messages.ts +195 -0
  65. package/src/wire-messages.ts +165 -0
  66. package/dist/node/headless/index.cjs +0 -1
  67. package/dist/node/headless/index.d.ts +0 -2
  68. package/dist/node/headless/index.js +0 -1
  69. package/dist/node/headless-DG_MEbf0.js +0 -19
  70. package/dist/node/headless-DG_MEbf0.js.map +0 -1
  71. package/dist/node/headless-xq77JBq0.cjs +0 -18
  72. package/dist/node/index-CrJ2Lhn3.d.ts +0 -96
  73. package/dist/node/index-CrJ2Lhn3.d.ts.map +0 -1
  74. package/dist/node/index-JIvzrtaA.d.ts +0 -52
  75. package/dist/node/index-JIvzrtaA.d.ts.map +0 -1
  76. package/dist/node/index-JTD_xtEQ.d.ts +0 -96
  77. package/dist/node/index-JTD_xtEQ.d.ts.map +0 -1
  78. package/dist/node/index-y_HBOfzO.d.ts +0 -52
  79. package/dist/node/index-y_HBOfzO.d.ts.map +0 -1
  80. package/dist/node/programmatic/index.cjs +0 -1
  81. package/dist/node/programmatic/index.d.ts +0 -2
  82. package/dist/node/programmatic/index.js +0 -1
  83. package/dist/node/programmatic-6wLf68qJ.cjs +0 -1
  84. package/dist/node/programmatic-70dxIXtS.js +0 -2
  85. package/dist/node/programmatic-70dxIXtS.js.map +0 -1
  86. package/dist/node/testing/index.cjs +0 -1
  87. package/dist/node/testing/index.d.ts +0 -2
  88. package/dist/node/testing/index.js +0 -1
  89. package/src/__tests__/programmatic/programmatic-ask-user.test.ts +0 -32
  90. package/src/__tests__/programmatic/programmatic-driver.test.ts +0 -107
  91. package/src/__tests__/programmatic/programmatic-flows.test.ts +0 -140
  92. package/src/__tests__/transport-registry.stop-all.test.ts +0 -53
  93. package/src/headless/HeadlessInteractionChannel.ts +0 -129
  94. package/src/headless/__tests__/headless-channel-options.test.ts +0 -106
  95. package/src/headless/__tests__/headless-goal-runner.test.ts +0 -108
  96. package/src/headless/__tests__/headless-provider-failure.integration.test.ts +0 -143
  97. package/src/headless/__tests__/headless-runner-initialization.test.ts +0 -45
  98. package/src/headless/__tests__/headless-runner.test.ts +0 -508
  99. package/src/headless/__tests__/headless-skill-activation.integration.test.ts +0 -430
  100. package/src/headless/__tests__/headless-transport.test.ts +0 -267
  101. package/src/headless/cli-input.ts +0 -50
  102. package/src/headless/headless-runner.ts +0 -230
  103. package/src/headless/headless-stream-json.ts +0 -148
  104. package/src/headless/headless-transport.ts +0 -43
  105. package/src/headless/index.ts +0 -8
  106. package/src/headless/print-terminal.ts +0 -57
  107. package/src/programmatic/ProgrammaticInteractionChannel.ts +0 -82
  108. package/src/programmatic/createProgrammaticAgent.ts +0 -71
  109. package/src/programmatic/index.ts +0 -5
  110. package/src/testing/index.ts +0 -10
  111. package/src/transport-registry.ts +0 -103
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/transport-registry.ts"],"mappings":";;;;;;cAiBa,iBAAA;EAAA,iBACM,OAAA;EAAA,iBACA,YAAA;cAEL,YAAA;EAIZ,QAAA,CAAS,SAAA,EAAW,sBAAA,CAAuB,mBAAA;EAI3C,MAAA,IAAU,eAAA,CAAgB,mBAAA;EAQ1B,UAAA,IAAc,sBAAA,CAAuB,mBAAA;EAM/B,UAAA,CAAW,IAAA,UAAc,OAAA,YAAmB,OAAA;EAS5C,UAAA,CAAW,IAAA,UAAc,OAAA,EAAS,MAAA,SAAe,eAAA,IAAmB,OAAA;EASpE,QAAA,CAAS,OAAA,EAAS,mBAAA,GAAsB,OAAA;EAT4B;;;;;EAsBpE,OAAA,IAAW,OAAA,CAAQ,cAAA;EAAA,QAajB,aAAA;EAAA,QASA,qBAAA;AAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/wire-messages.ts","../../src/outbound-delivery.ts","../../src/protocol-session.ts","../../src/usage-messages.ts","../../src/session-events.ts","../../src/session-message-handler.ts","../../src/message-decoders.ts","../../src/resume-buffer.ts","../../src/channel-frames.ts","../../src/session-resume-bridge.ts","../../src/peer-message-ledger.ts","../../src/handoff-chunking.ts"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{a as e,i as t,n,r,t as i}from"./headless-DG_MEbf0.js";import{n as a,t as o}from"./programmatic-70dxIXtS.js";import{readSettings as s,writeSettings as c}from"@robota-sdk/agent-framework";var l=class{entries=new Map;settingsPath;constructor(e){this.settingsPath=e}register(e){this.entries.set(e.name,e)}getAll(){let e=this.readTransportSettings();return Array.from(this.entries.values()).map(t=>({transport:t,config:this.resolveConfig(t,e[t.name])}))}getEnabled(){return this.getAll().filter(e=>e.config.enabled).map(e=>e.transport)}async setEnabled(e,t){let n=s(this.settingsPath),r=n.transports??{};r[e]={...r[e]??{},enabled:t},n.transports=r,c(this.settingsPath,n)}async setOptions(e,t){let n=s(this.settingsPath),r=n.transports??{};r[e]={...r[e]??{},options:t},n.transports=r,c(this.settingsPath,n)}async startAll(e){let t=this.getEnabled();for(let n of t)n.attach(e),await n.start()}async stopAll(){let e=[];for(let t of this.entries.values())try{await t.stop()}catch(t){e.push(t instanceof Error?t:Error(String(t)))}return{errors:e}}resolveConfig(e,t){return{enabled:t?.enabled??e.defaultEnabled,options:t?.options??{}}}readTransportSettings(){let e=s(this.settingsPath).transports;return!e||typeof e!=`object`||Array.isArray(e)?{}:e}};export{i as HeadlessInteractionChannel,e as PrintTerminal,a as ProgrammaticInteractionChannel,l as TransportRegistry,r as createHeadlessRunner,n as createHeadlessTransport,o as createProgrammaticAgent,t as promptInput};
1
+ function e(e,t,o){if(o.type===`get-background-tasks`){t({type:`background_tasks`,tasks:e.listBackgroundTasks(o.filter)});return}if(o.type===`get-background-task`){n(e,t,o);return}if(o.type===`get-background-job-groups`){t({type:`background_job_groups`,groups:e.listBackgroundJobGroups()});return}if(o.type===`get-background-job-group`){i(e,t,o);return}if(o.type===`wait-background-job-group`){a(e,t,o);return}r(e,t,o)}function t(e,t,n){if(!n.taskId){t({type:`protocol_error`,message:`taskId is required`});return}if(n.type===`cancel-background-task`){s(t,`cancel`,n.taskId,e.cancelBackgroundTask(n.taskId,n.reason));return}if(n.type===`close-background-task`){s(t,`close`,n.taskId,e.closeBackgroundTask(n.taskId));return}o(e,t,n)}function n(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 r(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 i(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 a(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 o(e,t,n){if(!n.input){t({type:`protocol_error`,message:`input is required`});return}s(t,`send`,n.taskId,e.sendBackgroundTask(n.taskId,n.input))}function s(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}))}const c=4*1024*1024;function l(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}const u=e=>typeof e==`string`,d=e=>typeof e==`string`&&e.length>0,f=e=>typeof e==`boolean`,p=e=>typeof e==`number`&&Number.isFinite(e),m=e=>t=>t===void 0||e(t),h=e=>Array.isArray(e)&&e.every(l),g=e=>e===null||l(e),_=e=>t=>typeof t==`string`&&e.includes(t),v=[`agent`,`process`,`scheduled`],y=[`foreground`,`background`],b=[`queued`,`running`,`waiting_permission`,`sleeping`,`paused`,`completed`,`failed`,`cancelled`],x=[`cancel`,`close`,`send`],S={submit:{prompt:d},command:{name:d,args:m(u)},abort:{},"cancel-queue":{},"get-messages":{},"get-context":{},"get-usage-report":{},"get-personal-usage-report":{requestId:d,period:_([`7d`,`30d`]),timezone:d},"get-stored-session-usage-report":{requestId:d,sessionId:d},"get-executing":{},"get-pending":{},"get-execution-workspace":{},"get-background-tasks":{filter:m(e=>l(e)&&m(_(v))(e.kind)&&m(_(b))(e.status)&&m(_(y))(e.mode)&&m(f)(e.includeClosed))},"get-background-task":{taskId:d},"get-background-job-groups":{},"get-background-job-group":{groupId:d},"wait-background-job-group":{groupId:d},"cancel-background-task":{taskId:d,reason:m(u)},"close-background-task":{taskId:d},"send-background-task":{taskId:d,input:e=>l(e)&&m(u)(e.prompt)&&m(u)(e.stdin)},"read-background-task-log":{taskId:d,cursor:m(e=>l(e)&&p(e.offset))},"permission-response":{id:d,result:e=>f(e)||e===`allow-session`||e===`allow-project`},"ask-response":{id:d,response:e=>l(e)&&(e.type===`answer`&&Array.isArray(e.values)&&e.values.every(u)&&m(u)(e.text)||e.type===`cancelled`&&e.values===void 0&&e.text===void 0)},resume:{lastSeq:p},ack:{seq:p}},C={driverId:m(u)},ee={text_delta:{delta:u,...C},user_message:{content:u,...C},tool_start:{state:l,...C},tool_end:{state:l,...C},thinking:{isThinking:f,...C},complete:{result:l,...C},interrupted:{result:l,...C},error:{message:u,...C},command_result:{name:u,message:u,success:f},messages:{messages:h},context:{state:l},usage_report:{report:l},personal_usage_report:{requestId:d,report:l},personal_usage_report_error:{requestId:d,code:_([`not_available`,`report_failed`]),message:u},stored_session_usage_report:{requestId:d,sessionId:d,report:l},stored_session_usage_report_error:{requestId:d,sessionId:d,code:_([`not_available`,`report_failed`]),message:u},executing:{executing:f},pending:{pending:e=>e===null||u(e)},execution_workspace_event:{snapshot:l},background_task_event:{event:l},background_job_group_event:{event:l},plan_event:{event:l},context_file_refreshed:{event:l},branch_event:{event:l},background_tasks:{tasks:h},background_task:{taskId:u,task:g},background_job_groups:{groups:h},background_job_group:{groupId:u,group:g},background_task_log:{taskId:u,page:l},permission_request:{event:l},ask_request:{event:l},prompt_resolved:{event:l},ui_intent:{event:l},session_renamed:{event:l},history_cleared:{},background_task_control_result:{action:_(x),taskId:u,success:f,message:m(u)},protocol_error:{message:u},resume_gap:{}};function w(e,t,n){if(!l(e))return{ok:!1,reason:`message must be a JSON object`};let r=e.type;if(!u(r))return{ok:!1,reason:`message type must be a string`};let i=Object.prototype.hasOwnProperty.call(t,r)?t[r]:void 0;if(i===void 0)return{ok:!1,reason:`Unknown message type: ${r}`};for(let[t,r]of Object.entries(i))if(!r(e[t]))return{ok:!1,reason:n(t)};return{ok:!0,message:e}}function T(e){return w(e,S,e=>e===`prompt`?`prompt must be a non-empty string`:`${e} has an invalid shape`)}function te(e){return w(e,ee,e=>`${e} has an invalid shape`)}function E(e,t){if(e.length>4194304)return{ok:!1,reason:`Frame too large`};let n;try{n=JSON.parse(e)}catch{return{ok:!1,reason:`Invalid JSON`}}return t(n)}function ne(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function D(e){return typeof e==`string`&&e.length>0?e:void 0}function re(e){if(!(new TextEncoder().encode(e).byteLength>4194304))try{let t=JSON.parse(e);if(!ne(t))return;let n=D(t.requestId);if(!n)return;if(t.type===`get-personal-usage-report`)return{type:`personal`,requestId:n};let r=D(t.sessionId);return t.type===`get-stored-session-usage-report`&&r?{type:`stored-session`,requestId:n,sessionId:r}:void 0}catch{return}}function ie(e,t,n,r){return n.type===`get-personal-usage-report`?(se(t,n,r.personalUsageReporter),!0):n.type===`get-stored-session-usage-report`?(oe(t,n,r.storedSessionUsageReporter),!0):n.type===`get-usage-report`?(ae(e,t,r.usageReporter),!0):!1}function ae(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:A(e)}))}catch(e){t({type:`protocol_error`,message:A(e instanceof Error?e:String(e))})}}function oe(e,t,n){if(!n){O(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=>O(e,t,`report_failed`,A(n)))}catch(n){O(e,t,`report_failed`,A(n instanceof Error?n:String(n)))}}function O(e,t,n,r){e({type:`stored_session_usage_report_error`,requestId:t.requestId,sessionId:t.sessionId,code:n,message:r})}function se(e,t,n){if(!n){k(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=>k(e,t.requestId,`report_failed`,A(n)))}catch(n){k(e,t.requestId,`report_failed`,A(n instanceof Error?n:String(n)))}}function k(e,t,n,r){e({type:`personal_usage_report_error`,requestId:t,code:n,message:r})}function A(e){return e instanceof Error?e.message:String(e)}function j(e,t){let n=E(e,T);if(n.ok)return n.message;let r=re(e);return r?.type===`personal`?(t({type:`personal_usage_report_error`,requestId:r.requestId,code:`report_failed`,message:`Invalid personal usage report request.`}),null):r?.type===`stored-session`?(t({type:`stored_session_usage_report_error`,requestId:r.requestId,sessionId:r.sessionId,code:`report_failed`,message:`Invalid stored-session usage report request.`}),null):(t({type:`protocol_error`,message:n.reason}),null)}const ce={text_delta:`forwarded`,tool_start:`forwarded`,tool_end:`forwarded`,thinking:`forwarded`,complete:`forwarded`,error:`forwarded`,context_update:`non-surface`,compact:`non-surface`,interrupted:`forwarded`,skill_activation:`non-surface`,background_task_event:`forwarded`,background_job_group_event:`forwarded`,execution_workspace_event:`forwarded`,user_message:`forwarded`,turn_source:`non-surface`,context_file_refreshed:`forwarded`,memory_event:`non-surface`,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`};function M(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:e,...r()}),u=e=>t({type:`interrupted`,result:e,...r()}),d=e=>t({type:`error`,message:e.message,...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=e=>t({type:`permission_request`,event:e}),y=e=>t({type:`ask_request`,event:e}),b=e=>t({type:`prompt_resolved`,event:e}),x=e=>{(e.requesterDriverId===void 0||e.requesterDriverId===n.getSurfaceDriverId?.())&&t({type:`ui_intent`,event:e})},S=e=>t({type:`session_renamed`,event:e}),C=()=>t({type:`history_cleared`});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`,_),e.on(`permission_request`,v),e.on(`ask_request`,y),e.on(`prompt_resolved`,b),e.on(`ui_intent`,x),e.on(`session_renamed`,S),e.on(`history_cleared`,C),()=>{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`,_),e.off(`permission_request`,v),e.off(`ask_request`,y),e.off(`prompt_resolved`,b),e.off(`ui_intent`,x),e.off(`session_renamed`,S),e.off(`history_cleared`,C)}}function le(e){return e.type===`get-messages`||e.type===`get-context`||e.type===`get-executing`||e.type===`get-pending`||e.type===`get-execution-workspace`}function ue(e,t,n){n.type===`get-messages`?t({type:`messages`,messages:e.getMessages()}):n.type===`get-context`?t({type:`context`,state:e.getContextState()}):n.type===`get-executing`?t({type:`executing`,executing:e.isExecuting()}):n.type===`get-execution-workspace`?t({type:`execution_workspace_event`,snapshot:e.getExecutionWorkspaceSnapshot()}):t({type:`pending`,pending:e.getPendingPrompt()})}function de(e){let t=M(e.session,e.deliver,{getSurfaceDriverId:()=>e.driverId});return{onMessage:fe(e.session,e.deliver,e.driverId,{personalUsageReporter:e.personalUsageReporter,usageReporter:e.usageReporter,storedSessionUsageReporter:e.storedSessionUsageReporter},e.surface),cleanup:t}}function fe(e,t,n,r=N,i){return a=>{let o=j(a,t);o&&P(e,t,o,n,r,i)}}const N={personalUsageReporter:void 0,usageReporter:void 0,storedSessionUsageReporter:void 0};function P(n,r,i,a,o=N,s){if(ie(n,r,i,o))return;if(pe(i)){R(n,r,i,a,s);return}if(le(i)){ue(n,r,i);return}if(me(i)){e(n,r,i);return}if(F(i)){t(n,r,i);return}if(I(i)){L(n,i,a);return}let c=i.type;r({type:`protocol_error`,message:`Unknown message type: ${c}`})}function pe(e){return e.type===`submit`||e.type===`command`||e.type===`abort`||e.type===`cancel-queue`}function me(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 F(e){return e.type===`cancel-background-task`||e.type===`close-background-task`||e.type===`send-background-task`}function I(e){return e.type===`permission-response`||e.type===`ask-response`}function L(e,t,n){t.type===`permission-response`?e.resolvePermission(t.id,t.result,n):e.resolveAsk(t.id,t.response,n)}function R(e,t,n,r,i){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).catch(e=>{t({type:`protocol_error`,message:e.message})})}else if(n.type===`command`){if(!n.name){t({type:`protocol_error`,message:`name is required`});return}e.executeCommand(n.name,n.args??``,`remote`,r).then(e=>{t({type:`command_result`,name:n.name,message:e?.message??`Unknown command: ${n.name}`,success:e?.success??!1,data:e?.data})},e=>{t({type:`protocol_error`,message:e.message})})}else n.type===`abort`?e.abort():e.cancelQueue()}const z=8*1048576;function B(e,t=z){return e!==void 0&&e>t}const V=120*1e3;function H(e=V,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 U(e,t,n,r=z,i=H()){let a=!1,o=t=>{if(a)return;let o=n?.();if(B(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 W=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 G=new TextEncoder,K=new TextDecoder,q=G.encode(`RBF`),he=1,J=4294967295;function Y(e,t){let n=G.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>J)throw Error(`seq must be an integer in 0..${J} (got ${t})`);return n}function X(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 ge(e){let t=Y(e.channel,e.seq),n=new Uint8Array(6+t.byteLength+4+e.payload.byteLength),r=X(n,1,t,e.seq);return n.set(e.payload,r),n}function _e(e){let t=Y(e.channel,e.seq),n=G.encode(JSON.stringify({event:e.event,payload:e.payload})),r=new Uint8Array(6+t.byteLength+4+n.byteLength),i=X(r,2,t,e.seq);return r.set(n,i),r}function Z(e){return e.byteLength<q.length?!1:q.every((t,n)=>e[n]===t)}function ve(e){let t=K.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 ye(e){if(!Z(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=K.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=ve(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 be=class{session;buffer;driverId;reporters;surface;unsubscribe;onDeliveryError;outbound;emitBoundary;disposed=!1;holding=!1;constructor(e){this.session=e.session,this.buffer=new W(e.buffer),this.driverId=e.driverId,this.surface=e.surface,this.reporters={personalUsageReporter:e.personalUsageReporter,usageReporter:e.usageReporter,storedSessionUsageReporter:e.storedSessionUsageReporter},this.emitBoundary=U(e=>this.emit(e),(e,t)=>this.reportDeliveryError(e,t)),this.unsubscribe=M(this.session,this.emitBoundary,{getSurfaceDriverId:()=>this.driverId})}attach(e,t){this.disposed||(this.onDeliveryError=t.onDeliveryError,this.outbound=U(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=j(e,this.emitBoundary);if(t){if(t.type===`resume`){this.replay(t.lastSeq);return}if(t.type===`ack`){this.buffer.ackThrough(t.seq);return}P(this.session,this.emitBoundary,t,this.driverId,this.reporters,this.surface)}}dispose(){this.disposed||(this.disposed=!0,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 Q(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 Se(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 Ce(e,t){e.origins.delete(t)}const $=16*1024;function we(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=Buffer.from(t,`utf8`),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:o.toString(`base64`)})}return a}var Te=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=Buffer.from(e.data,`base64`);if(n.toString(`base64`)!==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}):r.equals(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)}return Buffer.concat(t).toString(`utf8`)}};function Ee(e,t=$){return Math.max(1,Math.ceil(e.byteLength/t))}export{q as CHANNEL_FRAME_MAGIC,he as CHANNEL_FRAME_VERSION,$ as DEFAULT_MAX_CHUNK_BYTES,z as DEFAULT_MAX_PENDING_BYTES,V as DEFAULT_MAX_PENDING_MS,Te as HandoffChunkAssembler,c as MAX_INBOUND_FRAME_BYTES,ce as PROTOCOL_SESSION_EVENT_CLASSIFICATION,W as ResumeBuffer,be as SessionResumeBridge,Se as acknowledgePeerMessage,Q as admitPeerMessage,Ee as chunkCountFor,we as chunkHandoffPayload,U as createOutboundDelivery,xe as createPeerMessageLedger,H as createPendingStallClock,de as createSessionMessageHandler,ye as decodeChannelFrame,T as decodeClientMessage,E as decodeFrame,te as decodeServerMessage,ge as encodeBinaryFrame,_e as encodeChannelEventFrame,Ce as forgetPeerOrigin,Z as isChannelFrame,B as isOverPendingBudget};
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/transport-registry.ts"],"sourcesContent":["/**\n * TransportRegistry — manages IConfigurableTransport instances with settings-backed enable/disable.\n *\n * Settings file shape (under `transports` key in settings.json):\n * { \"ws\": { \"enabled\": true, \"options\": { \"port\": 7070 } } }\n */\n\nimport { readSettings, writeSettings, type TSettingsData } from '@robota-sdk/agent-framework';\n\nimport type { IDestroyResult, TUniversalValue } from '@robota-sdk/agent-core';\nimport type {\n IConfigurableTransport,\n IInteractiveSession,\n ITransportConfig,\n ITransportEntry,\n} from '@robota-sdk/agent-interface-transport';\n\nexport class TransportRegistry {\n private readonly entries = new Map<string, IConfigurableTransport<IInteractiveSession>>();\n private readonly settingsPath: string;\n\n constructor(settingsPath: string) {\n this.settingsPath = settingsPath;\n }\n\n register(transport: IConfigurableTransport<IInteractiveSession>): void {\n this.entries.set(transport.name, transport);\n }\n\n getAll(): ITransportEntry<IInteractiveSession>[] {\n const saved = this.readTransportSettings();\n return Array.from(this.entries.values()).map((transport) => ({\n transport,\n config: this.resolveConfig(transport, saved[transport.name]),\n }));\n }\n\n getEnabled(): IConfigurableTransport<IInteractiveSession>[] {\n return this.getAll()\n .filter((e) => e.config.enabled)\n .map((e) => e.transport);\n }\n\n async setEnabled(name: string, enabled: boolean): Promise<void> {\n const settings = readSettings(this.settingsPath);\n const transports = (settings.transports ?? {}) as TSettingsData;\n const entry = (transports[name] ?? {}) as TSettingsData;\n transports[name] = { ...entry, enabled } as TSettingsData;\n settings.transports = transports;\n writeSettings(this.settingsPath, settings);\n }\n\n async setOptions(name: string, options: Record<string, TUniversalValue>): Promise<void> {\n const settings = readSettings(this.settingsPath);\n const transports = (settings.transports ?? {}) as TSettingsData;\n const entry = (transports[name] ?? {}) as TSettingsData;\n transports[name] = { ...entry, options: options as TSettingsData } as TSettingsData;\n settings.transports = transports;\n writeSettings(this.settingsPath, settings);\n }\n\n async startAll(session: IInteractiveSession): Promise<void> {\n const enabled = this.getEnabled();\n for (const transport of enabled) {\n transport.attach(session);\n await transport.start();\n }\n }\n\n /**\n * Stop every registered transport — **best-effort** (CORE-013 disposal convention): one\n * transport's stop failure must not skip the others or reject a fire-and-forget caller.\n * Failures are collected into the returned result.\n */\n async stopAll(): Promise<IDestroyResult> {\n const errors: Error[] = [];\n for (const transport of this.entries.values()) {\n try {\n await transport.stop();\n } catch (error) {\n // allow-fallback: best-effort disposal IS the contract — the failure is collected into the returned result and the remaining transports still stop (CORE-013 convention)\n errors.push(error instanceof Error ? error : new Error(String(error)));\n }\n }\n return { errors };\n }\n\n private resolveConfig(\n transport: IConfigurableTransport<IInteractiveSession>,\n saved?: TSettingsData,\n ): ITransportConfig {\n const enabled = (saved?.enabled as boolean | undefined) ?? transport.defaultEnabled;\n const options = (saved?.options as Record<string, TUniversalValue> | undefined) ?? {};\n return { enabled, options };\n }\n\n private readTransportSettings(): Record<string, TSettingsData> {\n const settings = readSettings(this.settingsPath);\n const raw = settings.transports;\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};\n return raw as Record<string, TSettingsData>;\n }\n}\n"],"mappings":"iMAiBA,IAAa,EAAb,KAA+B,CAC7B,QAA2B,IAAI,IAC/B,aAEA,YAAY,EAAsB,CAChC,KAAK,aAAe,CACtB,CAEA,SAAS,EAA8D,CACrE,KAAK,QAAQ,IAAI,EAAU,KAAM,CAAS,CAC5C,CAEA,QAAiD,CAC/C,IAAM,EAAQ,KAAK,sBAAsB,EACzC,OAAO,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC,CAAC,CAAC,IAAK,IAAe,CAC3D,YACA,OAAQ,KAAK,cAAc,EAAW,EAAM,EAAU,KAAK,CAC7D,EAAE,CACJ,CAEA,YAA4D,CAC1D,OAAO,KAAK,OAAO,CAAC,CACjB,OAAQ,GAAM,EAAE,OAAO,OAAO,CAAC,CAC/B,IAAK,GAAM,EAAE,SAAS,CAC3B,CAEA,MAAM,WAAW,EAAc,EAAiC,CAC9D,IAAM,EAAW,EAAa,KAAK,YAAY,EACzC,EAAc,EAAS,YAAc,CAAC,EAE5C,EAAW,GAAQ,CAAE,GADN,EAAW,IAAS,CAAC,EACL,SAAQ,EACvC,EAAS,WAAa,EACtB,EAAc,KAAK,aAAc,CAAQ,CAC3C,CAEA,MAAM,WAAW,EAAc,EAAyD,CACtF,IAAM,EAAW,EAAa,KAAK,YAAY,EACzC,EAAc,EAAS,YAAc,CAAC,EAE5C,EAAW,GAAQ,CAAE,GADN,EAAW,IAAS,CAAC,EACI,SAAyB,EACjE,EAAS,WAAa,EACtB,EAAc,KAAK,aAAc,CAAQ,CAC3C,CAEA,MAAM,SAAS,EAA6C,CAC1D,IAAM,EAAU,KAAK,WAAW,EAChC,IAAK,IAAM,KAAa,EACtB,EAAU,OAAO,CAAO,EACxB,MAAM,EAAU,MAAM,CAE1B,CAOA,MAAM,SAAmC,CACvC,IAAM,EAAkB,CAAC,EACzB,IAAK,IAAM,KAAa,KAAK,QAAQ,OAAO,EAC1C,GAAI,CACF,MAAM,EAAU,KAAK,CACvB,OAAS,EAAO,CAEd,EAAO,KAAK,aAAiB,MAAQ,EAAY,MAAM,OAAO,CAAK,CAAC,CAAC,CACvE,CAEF,MAAO,CAAE,QAAO,CAClB,CAEA,cACE,EACA,EACkB,CAGlB,MAAO,CAAE,QAFQ,GAAO,SAAmC,EAAU,eAEnD,QADD,GAAO,SAA2D,CAAC,CAC1D,CAC5B,CAEA,uBAA+D,CAE7D,IAAM,EADW,EAAa,KAAK,YAChB,CAAC,CAAC,WAErB,MADI,CAAC,GAAO,OAAO,GAAQ,UAAY,MAAM,QAAQ,CAAG,EAAU,CAAC,EAC5D,CACT,CACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/background-messages.ts","../../src/message-decoders.ts","../../src/usage-messages.ts","../../src/message-parser.ts","../../src/session-events.ts","../../src/session-query-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/handoff-chunking.ts"],"sourcesContent":["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","/**\n * Owner-side runtime decoders for the wire message unions (issue #2045 / TRANS-005 adjacent).\n *\n * `wire-messages.ts` owns the TypeScript unions; this file owns turning `JSON.parse` output into one\n * of them. Before it, the server did `JSON.parse(data) as TClientMessage` and each client did the\n * same for `TServerMessage`, so valid JSON with an invalid shape crossed the boundary wearing a\n * protocol type. Carriers now implement `raw string → owner decoder → typed message` and answer a\n * refusal with the protocol's own `protocol_error` (server side) or client error (client side).\n *\n * `decodeClientMessage` is TOTAL: every variant and every nested field it carries is validated.\n * `decodeServerMessage` validates every variant's discriminator and declared top-level fields; a\n * payload OWNED BY ANOTHER PACKAGE (`IToolState`, `IExecutionResult`, background events, …) is\n * checked as a non-array record (or array of records) here — its own total decoding belongs to its\n * owner and is tracked there, not duplicated in a transport package.\n */\n\nimport type { TBackgroundControlAction, TClientMessage, TServerMessage } from './wire-messages.js';\n\nexport type TMessageDecodeResult<TMessage> =\n | { readonly ok: true; readonly message: TMessage }\n | { readonly ok: false; readonly reason: string };\n\n/** Serialized frames larger than this are refused before any parse (a bounded inbound budget). */\nexport const MAX_INBOUND_FRAME_BYTES = 4 * 1024 * 1024;\n\ntype TRecord = Record<string, unknown>;\n\nfunction isRecord(value: unknown): value is TRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\nconst isString = (v: unknown): v is string => typeof v === 'string';\nconst isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.length > 0;\nconst isBoolean = (v: unknown): v is boolean => typeof v === 'boolean';\nconst isFiniteNumber = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);\nconst isOptional =\n (check: (v: unknown) => boolean) =>\n (v: unknown): boolean =>\n v === undefined || check(v);\nconst isRecordArray = (v: unknown): boolean => Array.isArray(v) && v.every(isRecord);\nconst isNullableRecord = (v: unknown): boolean => v === null || isRecord(v);\nconst oneOf =\n (members: readonly string[]) =>\n (v: unknown): boolean =>\n typeof v === 'string' && members.includes(v);\n\ntype TFieldCheck = (value: unknown) => boolean;\ntype TVariantShape = Readonly<Record<string, TFieldCheck>>;\n\nconst BACKGROUND_TASK_KINDS = ['agent', 'process', 'scheduled'] as const;\nconst BACKGROUND_TASK_MODES = ['foreground', 'background'] as const;\nconst BACKGROUND_TASK_STATUSES = [\n 'queued',\n 'running',\n 'waiting_permission',\n 'sleeping',\n 'paused',\n 'completed',\n 'failed',\n 'cancelled',\n] as const;\nconst BACKGROUND_CONTROL_ACTIONS: readonly TBackgroundControlAction[] = ['cancel', 'close', 'send'];\n\nconst isBackgroundTaskListFilter: TFieldCheck = (v) =>\n isRecord(v) &&\n isOptional(oneOf(BACKGROUND_TASK_KINDS))(v['kind']) &&\n isOptional(oneOf(BACKGROUND_TASK_STATUSES))(v['status']) &&\n isOptional(oneOf(BACKGROUND_TASK_MODES))(v['mode']) &&\n isOptional(isBoolean)(v['includeClosed']);\nconst isBackgroundTaskInput: TFieldCheck = (v) =>\n isRecord(v) && isOptional(isString)(v['prompt']) && isOptional(isString)(v['stdin']);\nconst isBackgroundTaskLogCursor: TFieldCheck = (v) => isRecord(v) && isFiniteNumber(v['offset']);\nconst isPermissionResultValue: TFieldCheck = (v) =>\n isBoolean(v) || v === 'allow-session' || v === 'allow-project';\nconst isActionResponse: TFieldCheck = (v) =>\n isRecord(v) &&\n ((v['type'] === 'answer' &&\n Array.isArray(v['values']) &&\n v['values'].every(isString) &&\n isOptional(isString)(v['text'])) ||\n (v['type'] === 'cancelled' && v['values'] === undefined && v['text'] === undefined));\n\n/** One entry per `TClientMessage` variant — a variant added to the union without one fails the test. */\nexport const CLIENT_MESSAGE_SHAPES: Readonly<Record<TClientMessage['type'], TVariantShape>> = {\n submit: { prompt: isNonEmptyString },\n command: { name: isNonEmptyString, args: isOptional(isString) },\n abort: {},\n 'cancel-queue': {},\n 'get-messages': {},\n 'get-context': {},\n 'get-usage-report': {},\n 'get-personal-usage-report': {\n requestId: isNonEmptyString,\n period: oneOf(['7d', '30d']),\n timezone: isNonEmptyString,\n },\n 'get-stored-session-usage-report': {\n requestId: isNonEmptyString,\n sessionId: isNonEmptyString,\n },\n 'get-executing': {},\n 'get-pending': {},\n 'get-execution-workspace': {},\n 'get-background-tasks': { filter: isOptional(isBackgroundTaskListFilter) },\n 'get-background-task': { taskId: isNonEmptyString },\n 'get-background-job-groups': {},\n 'get-background-job-group': { groupId: isNonEmptyString },\n 'wait-background-job-group': { groupId: isNonEmptyString },\n 'cancel-background-task': { taskId: isNonEmptyString, reason: isOptional(isString) },\n 'close-background-task': { taskId: isNonEmptyString },\n 'send-background-task': { taskId: isNonEmptyString, input: isBackgroundTaskInput },\n 'read-background-task-log': {\n taskId: isNonEmptyString,\n cursor: isOptional(isBackgroundTaskLogCursor),\n },\n 'permission-response': { id: isNonEmptyString, result: isPermissionResultValue },\n 'ask-response': { id: isNonEmptyString, response: isActionResponse },\n resume: { lastSeq: isFiniteNumber },\n ack: { seq: isFiniteNumber },\n};\n\nconst authored: TVariantShape = { driverId: isOptional(isString) };\n\n/** One entry per `TServerMessage` variant. Foreign-owned payloads are checked as records (see header). */\nexport const SERVER_MESSAGE_SHAPES: Readonly<Record<TServerMessage['type'], TVariantShape>> = {\n text_delta: { delta: isString, ...authored },\n user_message: { content: isString, ...authored },\n tool_start: { state: isRecord, ...authored },\n tool_end: { state: isRecord, ...authored },\n thinking: { isThinking: isBoolean, ...authored },\n complete: { result: isRecord, ...authored },\n interrupted: { result: isRecord, ...authored },\n error: { message: isString, ...authored },\n command_result: { name: isString, message: isString, success: isBoolean },\n messages: { messages: isRecordArray },\n context: { state: isRecord },\n usage_report: { report: isRecord },\n personal_usage_report: { requestId: isNonEmptyString, report: isRecord },\n personal_usage_report_error: {\n requestId: isNonEmptyString,\n code: oneOf(['not_available', 'report_failed']),\n message: isString,\n },\n stored_session_usage_report: {\n requestId: isNonEmptyString,\n sessionId: isNonEmptyString,\n report: isRecord,\n },\n stored_session_usage_report_error: {\n requestId: isNonEmptyString,\n sessionId: isNonEmptyString,\n code: oneOf(['not_available', 'report_failed']),\n message: isString,\n },\n executing: { executing: isBoolean },\n pending: { pending: (v) => v === null || isString(v) },\n execution_workspace_event: { snapshot: isRecord },\n background_task_event: { event: isRecord },\n background_job_group_event: { event: isRecord },\n plan_event: { event: isRecord },\n context_file_refreshed: { event: isRecord },\n branch_event: { event: isRecord },\n background_tasks: { tasks: isRecordArray },\n background_task: { taskId: isString, task: isNullableRecord },\n background_job_groups: { groups: isRecordArray },\n background_job_group: { groupId: isString, group: isNullableRecord },\n background_task_log: { taskId: isString, page: isRecord },\n permission_request: { event: isRecord },\n ask_request: { event: isRecord },\n prompt_resolved: { event: isRecord },\n ui_intent: { event: isRecord },\n session_renamed: { event: isRecord },\n history_cleared: {},\n background_task_control_result: {\n action: oneOf(BACKGROUND_CONTROL_ACTIONS),\n taskId: isString,\n success: isBoolean,\n message: isOptional(isString),\n },\n protocol_error: { message: isString },\n resume_gap: {},\n};\n\nfunction decodeVariant<TMessage extends { type: string }>(\n value: unknown,\n shapes: Readonly<Record<string, TVariantShape>>,\n reasonFor: (field: string) => string,\n): TMessageDecodeResult<TMessage> {\n if (!isRecord(value)) return { ok: false, reason: 'message must be a JSON object' };\n const type = value['type'];\n if (!isString(type)) return { ok: false, reason: 'message type must be a string' };\n const shape = Object.prototype.hasOwnProperty.call(shapes, type) ? shapes[type] : undefined;\n if (shape === undefined) return { ok: false, reason: `Unknown message type: ${type}` };\n for (const [field, check] of Object.entries(shape)) {\n if (!check(value[field])) return { ok: false, reason: reasonFor(field) };\n }\n return { ok: true, message: value as unknown as TMessage };\n}\n\n/** Total decode of a parsed client frame. */\nexport function decodeClientMessage(value: unknown): TMessageDecodeResult<TClientMessage> {\n return decodeVariant<TClientMessage>(value, CLIENT_MESSAGE_SHAPES, (field) =>\n field === 'prompt' ? 'prompt must be a non-empty string' : `${field} has an invalid shape`,\n );\n}\n\n/** Variant-level decode of a parsed server frame (foreign-owned payloads as records, see header). */\nexport function decodeServerMessage(value: unknown): TMessageDecodeResult<TServerMessage> {\n return decodeVariant<TServerMessage>(\n value,\n SERVER_MESSAGE_SHAPES,\n (field) => `${field} has an invalid shape`,\n );\n}\n\n/** `raw string → JSON → owner decoder`, the one path a carrier implements. */\nexport function decodeFrame<TMessage>(\n data: string,\n decode: (value: unknown) => TMessageDecodeResult<TMessage>,\n): TMessageDecodeResult<TMessage> {\n if (data.length > MAX_INBOUND_FRAME_BYTES) return { ok: false, reason: 'Frame too large' };\n let parsed: unknown;\n try {\n parsed = JSON.parse(data);\n // allow-fallback: the parse failure is returned as a typed refusal the carrier answers on the wire\n } catch {\n return { ok: false, reason: 'Invalid JSON' };\n }\n return decode(parsed);\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 * 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 type { TOutboundDeliver } from './outbound-delivery.js';\nimport type { IProtocolSession } from './protocol-session.js';\nimport type {\n IExecutionWorkspaceEvent,\n TBackgroundJobGroupEvent,\n TBackgroundTaskEvent,\n} from '@robota-sdk/agent-interface-execution';\nimport type { TDriverId, TInteractiveEventName } from '@robota-sdk/agent-interface-session';\nimport type {\n IAskRequestEvent,\n IBranchEvent,\n IContextFileRefreshedEvent,\n IExecutionResult,\n IPermissionRequestEvent,\n IPromptResolvedEvent,\n IPlanApprovalEvent,\n ISessionRenamedEvent,\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 complete: 'forwarded',\n error: 'forwarded',\n context_update: 'non-surface',\n compact: 'non-surface',\n interrupted: 'forwarded',\n skill_activation: 'non-surface',\n background_task_event: 'forwarded',\n background_job_group_event: 'forwarded',\n execution_workspace_event: 'forwarded',\n user_message: 'forwarded',\n turn_source: 'non-surface',\n context_file_refreshed: 'forwarded',\n memory_event: 'non-surface',\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} 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\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 // which is identical for every forwarded event (asserted in `session-event-delivery.test.ts`). Keeping\n // the argument would have been a parameter that 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, ...attr() });\n const onInterrupted = (result: IExecutionResult): void =>\n deliver({ type: 'interrupted', result, ...attr() });\n const onError = (error: Error): void =>\n deliver({ type: 'error', message: error.message, ...attr() });\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 const onPermissionRequest = (event: IPermissionRequestEvent): void =>\n deliver({ type: 'permission_request', event });\n const onAskRequest = (event: IAskRequestEvent): void => deliver({ type: 'ask_request', event });\n const onPromptResolved = (event: IPromptResolvedEvent): void =>\n deliver({ type: 'prompt_resolved', event });\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\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 session.on('permission_request', onPermissionRequest);\n session.on('ask_request', onAskRequest);\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\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 session.off('permission_request', onPermissionRequest);\n session.off('ask_request', onAskRequest);\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 };\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 TSessionQueryMessage = Extract<\n TClientMessage,\n {\n type:\n 'get-messages' | 'get-context' | 'get-executing' | 'get-pending' | 'get-execution-workspace';\n }\n>;\n\nexport function isSessionQueryMessage(msg: TClientMessage): msg is TSessionQueryMessage {\n return (\n msg.type === 'get-messages' ||\n msg.type === 'get-context' ||\n msg.type === 'get-executing' ||\n msg.type === 'get-pending' ||\n msg.type === 'get-execution-workspace'\n );\n}\n\nexport function handleSessionQueryMessage(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: TSessionQueryMessage,\n): void {\n if (msg.type === 'get-messages') {\n deliver({ type: 'messages', messages: session.getMessages() });\n } else if (msg.type === 'get-context') {\n deliver({ type: 'context', state: session.getContextState() });\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 {\n deliver({ type: 'pending', pending: session.getPendingPrompt() });\n }\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 handleBackgroundControlMessage,\n handleBackgroundQueryMessage,\n} from './background-messages.js';\nimport { parseClientMessage } from './message-parser.js';\nimport { subscribeSessionEvents } from './session-events.js';\nimport { handleSessionQueryMessage, isSessionQueryMessage } from './session-query-messages.js';\nimport { handleUsageQueryMessage } from './usage-messages.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 { TClientMessage } from './wire-messages.js';\nimport type { TUsageSurface } from '@robota-sdk/agent-interface-analytics';\nimport type { 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\nexport interface ISessionMessageHandlerOptions {\n /** IProtocolSession to expose. */\n session: IProtocolSession;\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 /** 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}\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 cleanup = subscribeSessionEvents(options.session, options.deliver, {\n getSurfaceDriverId: () => options.driverId,\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 );\n\n return { onMessage, cleanup };\n}\n\nfunction createMessageHandler(\n session: IProtocolSession,\n deliver: TOutboundDeliver,\n driverId?: TDriverId,\n reporters: IUsageQueryReporters = EMPTY_USAGE_REPORTERS,\n surface?: TUsageSurface,\n): (data: string) => void {\n return (data: string): void => {\n const msg = parseClientMessage(data, deliver);\n if (!msg) return;\n handleClientMessage(session, deliver, msg, driverId, reporters, surface);\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: IProtocolSession,\n deliver: TOutboundDeliver,\n msg: TClientMessage,\n driverId?: TDriverId,\n reporters: IUsageQueryReporters = EMPTY_USAGE_REPORTERS,\n surface?: TUsageSurface,\n): void {\n if (handleUsageQueryMessage(session, deliver, msg, reporters)) {\n return;\n }\n if (isSessionControlMessage(msg)) {\n handleSessionControlMessage(session, deliver, msg, driverId, surface);\n return;\n }\n if (isSessionQueryMessage(msg)) {\n handleSessionQueryMessage(session, deliver, msg);\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): 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 .catch((error: Error) => {\n deliver({ type: 'protocol_error', message: error.message });\n });\n } else if (msg.type === 'command') {\n if (!msg.name) {\n deliver({ type: 'protocol_error', message: 'name is required' });\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).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 });\n },\n (error: Error) => {\n deliver({ type: 'protocol_error', message: error.message });\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 — a session event's own name for the fan-out (they are identical), and the\n * 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 * 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 { 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}\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 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.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 );\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 // 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 * 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/**\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 = Buffer.from(serialized, 'utf8');\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: slice.toString('base64') });\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, Buffer>();\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 = Buffer.from(chunk.data, 'base64');\n // Buffer.from is lenient — it drops what it cannot parse rather than throwing, so a mangled\n // chunk would silently become a shorter one and corrupt the payload in a way only the final\n // digest would catch, after the whole transfer completed. Re-encoding is how the leniency is\n // detected here instead.\n if (decoded.toString('base64') !== 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 (held.equals(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: Buffer[] = [];\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 return Buffer.concat(ordered).toString('utf8');\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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"}
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("node:crypto"),t=require("jose"),n=require("@robota-sdk/agent-core/node");function r(){let t=(0,e.randomBytes)(32).toString(`hex`);if(t.length!==64)throw Error(`transport admission: could not mint a credential of the required length`);return t}function i(e={}){let t=`open`in e&&e.open===!0,n=e.token!==void 0&&e.token!==null&&e.token!==``;if(n&&t)throw Error("transport admission: a token and `open: true` are contradictory — one requires a credential and the other requires none. Asking for both means one of the two beliefs behind the call is wrong; say which is intended.");if(n)return{token:e.token};if(e.token===null||t){let t=e.openReason?.trim();if(!t)throw Error("transport admission: an open transport requires `openReason`. An open transport is a decision, and a decision nobody wrote down is indistinguishable from an oversight.");return{token:null,openReason:t}}return{token:r()}}function a(t,n){if(n===void 0)return!1;let r=Buffer.from(t,`utf8`),i=Buffer.from(n,`utf8`);return r.length===i.length&&(0,e.timingSafeEqual)(r,i)}function o(e){if(!e||e.slice(0,6).toLowerCase()!==`bearer`)return;let t=e.slice(6);if(t===``||t[0]!==` `&&t[0]!==` `)return;let n=t.replace(/^[ \t]+/,``);return n===``?void 0:n}function s(t){let n=JSON.stringify(t),r=Buffer.from(n,`utf8`);return{serialized:n,integrity:{digest:(0,e.createHash)(`sha256`).update(r).digest(`base64url`),byteLength:r.byteLength}}}function c(t,n){let r=Buffer.from(t,`utf8`);return r.byteLength===n.byteLength?(0,e.createHash)(`sha256`).update(r).digest(`base64url`)===n.digest?{intact:!0}:{intact:!1,failure:`digest-mismatch`}:{intact:!1,failure:`truncated`,expectedBytes:n.byteLength,actualBytes:r.byteLength}}const l=10*6e4,u=`at+jwt`,d=[`RS256`,`ES256`,`EdDSA`],f={RS256:{kty:`RSA`},ES256:{kty:`EC`,crv:`P-256`},EdDSA:{kty:`OKP`,crv:`Ed25519`}};var p=class{refusal;constructor(e){this.refusal=e}};function m(e,r={}){let i=h(e),a=r.now??Date.now,o=r.monotonicNow??(()=>performance.now()),s={fetch:r.fetch,lookup:r.lookup},c=i.hostname.replace(/^\[|\]$/g,``).toLowerCase(),d={keys:void 0,jwksUri:void 0,fetchedAt:void 0,lastAttemptAt:void 0,inflight:void 0},f=new Set(e.allowedSubjects??[]),m=new Set(e.allowedClients??[]);async function S(e){if(new URL(e).protocol!==`https:`)return;let t=await(0,n.fetchWithEgressPolicy)(e,{headers:{accept:`application/json`},timeoutMs:1e4,maxResponseBytes:65536},{allowedHosts:[c],maxRedirects:0},s);if(!(!t.ok||t.status!==200))return JSON.parse(new TextDecoder().decode(t.body))}async function C(){for(let t of g(i)){let n=await S(t).catch(()=>void 0);if(!x(n))continue;if(n.issuer!==e.issuer)return;let r=n.jwks_uri;return typeof r!=`string`||!URL.canParse(r)?void 0:new URL(r).protocol===`https:`?r:void 0}}function w(){return d.fetchedAt===void 0?1/0:o()-d.fetchedAt}async function T(){d.lastAttemptAt=o();try{if(w()>=l&&(d.jwksUri=void 0),d.jwksUri??=await C(),d.jwksUri===void 0)return;let e=_(await S(d.jwksUri));if(e===void 0)return;d.keys=e,d.fetchedAt=o()}catch{}}async function E(){if(d.inflight!==void 0)return d.inflight;if(!(d.lastAttemptAt!==void 0&&o()-d.lastAttemptAt<3e4))return d.inflight=T().finally(()=>{d.inflight=void 0}),d.inflight}async function D(){return w()>=l&&await E(),w()>=36e5&&(d.keys=void 0,d.jwksUri=void 0,d.fetchedAt=void 0),d.keys}async function O(e,t){let n=await D();if(n===void 0)throw new p(`keys-unavailable`);if(e===void 0){if(n.length>1)throw new p(`ambiguous-key`);let e=n[0];if(e===void 0)throw new p(`unknown-key`);if(!v(e,t))throw new p(`key-mismatch`);return e}let r=n.filter(t=>t.kid===e);if(r.length===0&&(await E(),n=d.keys??[],r=n.filter(t=>t.kid===e)),r.length===0)throw new p(`unknown-key`);let i=r.find(e=>v(e,t));if(i===void 0)throw new p(`key-mismatch`);return i}async function k(n){if(typeof n!=`string`||n.length===0)throw new p(`malformed`);if(n.length>8192)throw new p(`oversize`);let r;try{r=(0,t.decodeProtectedHeader)(n)}catch{throw new p(`malformed`)}if(typeof r.typ!=`string`||b(r.typ)!==u)throw new p(`wrong-type`);let i=e.algorithms.find(e=>e===r.alg);if(i===void 0)throw new p(`unsupported-algorithm`);if(r.kid!==void 0&&typeof r.kid!=`string`)throw new p(`malformed`);let o=await O(r.kid,i),s;try{s=await(0,t.importJWK)(o,i)}catch{throw new p(`key-mismatch`)}let c;try{({payload:c}=await(0,t.jwtVerify)(n,s,{issuer:e.issuer,audience:e.resource,algorithms:[i],typ:u,clockTolerance:60,currentDate:new Date(a()),requiredClaims:[`exp`,`sub`,`client_id`]}))}catch(e){throw new p(y(e))}let l=typeof c.scope==`string`?c.scope.split(` `):[];if(!e.requiredScopes.every(e=>l.includes(e)))throw new p(`missing-scope`);let d=c.sub,h=c.client_id;if(!(typeof d==`string`&&f.has(d)||typeof h==`string`&&m.has(h)))throw new p(`principal-not-allowed`);return{admitted:!0}}return{async verify(e){try{return await k(e)}catch(e){return{admitted:!1,refusal:e instanceof p?e.refusal:`malformed`}}}}}function h(e){if(!URL.canParse(e.issuer))throw Error(`access-token verifier: issuer is not a URL`);let t=new URL(e.issuer);if(t.protocol!==`https:`||t.search!==``||t.hash!==``)throw Error(`access-token verifier: issuer must be an https URL with no query or fragment`);if(!URL.canParse(e.resource))throw Error(`access-token verifier: resource is not a URL`);if(e.algorithms.length===0||!e.algorithms.every(e=>d.includes(e)))throw Error(`access-token verifier: algorithms must be a non-empty subset of RS256, ES256, EdDSA`);if(e.requiredScopes.length===0)throw Error(`access-token verifier: at least one required scope is needed`);if((e.allowedSubjects?.length??0)+(e.allowedClients?.length??0)===0)throw Error(`access-token verifier: name at least one allowed subject or client — this host is single-tenant, and issuer + audience + scope alone admit anyone the issuer serves`);return t}function g(e){let t=e.pathname===`/`?``:e.pathname.replace(/\/$/,``);return[`${e.origin}/.well-known/oauth-authorization-server${t}`,`${e.origin}${t}/.well-known/openid-configuration`]}function _(e){if(!(!x(e)||!Array.isArray(e.keys)||e.keys.length>32))return e.keys.filter(e=>x(e)&&typeof e.kty==`string`)}function v(e,t){let n=f[t];return e.kty!==n.kty||n.crv!==void 0&&e.crv!==n.crv||e.alg!==void 0&&e.alg!==t||e.use!==void 0&&e.use!==`sig`||e.key_ops!==void 0&&!e.key_ops.includes(`verify`)?!1:e.d===void 0}function y(e){let t=x(e)?e.code:void 0,n=x(e)?e.claim:void 0;switch(t){case`ERR_JWT_EXPIRED`:return`expired`;case`ERR_JWS_SIGNATURE_VERIFICATION_FAILED`:return`bad-signature`;case`ERR_JOSE_ALG_NOT_ALLOWED`:return`unsupported-algorithm`;case`ERR_JWT_CLAIM_VALIDATION_FAILED`:return n===`iss`?`wrong-issuer`:n===`aud`?`wrong-audience`:n===`nbf`?`not-yet-valid`:n===`typ`?`wrong-type`:`malformed`;default:return`malformed`}}function b(e){let t=e.toLowerCase();return t.startsWith(`application/`)?t.slice(12):t}function x(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}exports.bearerCredential=o,exports.createAccessTokenVerifier=m,exports.credentialMatches=a,exports.mintTransportToken=r,exports.resolveAdmission=i,exports.sealHandoffRecord=s,exports.verifyHandoffPayload=c;
@@ -0,0 +1,109 @@
1
+ import { IInteractiveSessionRecord } from "@robota-sdk/agent-interface-session";
2
+ import { IAccessTokenVerifier, IAccessTokenVerifierConfig, ITransportAdmission, ITransportAdmissionConfig } from "@robota-sdk/agent-interface-transport";
3
+ import { IHandoffIntegrity } from "@robota-sdk/agent-interface-session-mobility";
4
+ import { IEgressDeps } from "@robota-sdk/agent-core/node";
5
+ //#region src/node/admission.d.ts
6
+ /**
7
+ * Mint a per-launch credential.
8
+ *
9
+ * Throws rather than returning a short or empty string if entropy is unavailable, so a transport
10
+ * built on it fails to construct instead of binding with a guessable credential.
11
+ */
12
+ declare function mintTransportToken(): string;
13
+ /**
14
+ * Resolve the admission decision for one transport.
15
+ *
16
+ * IDEMPOTENT: it accepts an already-resolved `ITransportAdmission` and returns it unchanged, so
17
+ * `resolve(resolve(x))` is `resolve(x)`. That is not a convenience — it is what lets a caller
18
+ * holding either shape just call this, instead of deciding which shape it holds first.
19
+ *
20
+ * The HTTP route used to make that decision with `'token' in options.admission`, and review showed
21
+ * it cannot work: BOTH interfaces declare a `token`, so a config is only distinguishable from a
22
+ * resolution by the VALUE. `{ token: '' }` — documented as "mint a fresh one" — was read as
23
+ * pre-resolved and installed the empty string as the required credential, which a peer sending an
24
+ * empty bearer would then match. Making this function idempotent deletes that discriminator
25
+ * instead of repairing it.
26
+ *
27
+ * @throws if `open` is set without a reason — an unexplained open transport is the state this
28
+ * module exists to remove, so it is refused at construction rather than recorded and shipped.
29
+ * @throws if a token and `open: true` are asked for together. The two are contradictory, and the
30
+ * silent precedence this used to give (token wins) is the safe DIRECTION but the wrong ANSWER: a
31
+ * caller that wrote both does not know what it is asking for, and one of the two things it believes
32
+ * is false. `WebRtcTransport` already refused the analogous `secret` + `open` pair in its own
33
+ * constructor; a seam whose whole claim is "one place to read and one place to change" cannot leave
34
+ * that protection to whichever transport happens to have implemented it.
35
+ */
36
+ declare function resolveAdmission(config?: ITransportAdmissionConfig | ITransportAdmission): ITransportAdmission;
37
+ /**
38
+ * Whether a presented credential matches the required one.
39
+ *
40
+ * Compared with `timingSafeEqual` over the raw bytes, so the answer takes the same time whatever the
41
+ * first differing character is. The length check before it is unavoidable — `timingSafeEqual` throws
42
+ * on unequal lengths — and it does leak one bit: whether the presented credential is the right
43
+ * LENGTH.
44
+ *
45
+ * For a minted token that costs nothing: the length is fixed at 64 hex characters and published in
46
+ * this file. A host that supplies its own token of some other length leaks that length instead, and
47
+ * review was right to point out that the earlier wording ("fixed and public") quietly assumed the
48
+ * minted case for every case. Length is not the secret in either — but the claim now says which
49
+ * situation it is describing.
50
+ *
51
+ * A missing credential is a mismatch, not a special case: a peer that sends nothing and a peer that
52
+ * sends the wrong thing are equally unadmitted, and giving them different answers would tell an
53
+ * attacker which half they got right.
54
+ */
55
+ declare function credentialMatches(expected: string, presented: string | undefined): boolean;
56
+ declare function bearerCredential(header: string | undefined | null): string | undefined;
57
+ //#endregion
58
+ //#region src/node/handoff-manifest.d.ts
59
+ /**
60
+ * Serialize a record and describe what was serialized.
61
+ *
62
+ * The string is returned alongside the integrity data because the digest is only meaningful for
63
+ * these exact bytes. A caller that re-serialized the record before sending could produce a
64
+ * different key order and a digest mismatch on a payload that is perfectly correct — a corruption
65
+ * report caused by the corruption check.
66
+ */
67
+ declare function sealHandoffRecord(record: IInteractiveSessionRecord): {
68
+ serialized: string;
69
+ integrity: IHandoffIntegrity;
70
+ };
71
+ /** Why a received payload was not accepted. */
72
+ type TIntegrityFailure = 'truncated' | 'digest-mismatch';
73
+ interface IIntegrityVerdict {
74
+ readonly intact: boolean;
75
+ readonly failure?: TIntegrityFailure;
76
+ /** What the manifest promised and what arrived, so a report can say more than "it failed". */
77
+ readonly expectedBytes?: number;
78
+ readonly actualBytes?: number;
79
+ }
80
+ /**
81
+ * Check a received payload against the manifest's integrity metadata.
82
+ *
83
+ * Nothing is parsed here. Parsing a payload to check it is well-formed would mean building the
84
+ * object graph before knowing whether the bytes are the ones that were sent, and a destination that
85
+ * has already parsed a corrupt payload has already spent the effort this check exists to avoid.
86
+ */
87
+ declare function verifyHandoffPayload(serialized: string, integrity: IHandoffIntegrity): IIntegrityVerdict;
88
+ //#endregion
89
+ //#region src/node/access-token-verifier.d.ts
90
+ /** What the verifier reaches outward through. Injected in tests; defaults to the real network. */
91
+ interface IAccessTokenVerifierDeps {
92
+ readonly fetch?: IEgressDeps['fetch'];
93
+ readonly lookup?: IEgressDeps['lookup'];
94
+ /** Wall-clock milliseconds since the epoch, for `exp`/`nbf`. Defaults to `Date.now`. */
95
+ readonly now?: () => number;
96
+ /** Monotonic milliseconds, for cache age and refetch intervals. Defaults to `performance.now`. */
97
+ readonly monotonicNow?: () => number;
98
+ }
99
+ /**
100
+ * Build a verifier for one issuer and one resource.
101
+ *
102
+ * @throws when the configuration cannot describe a safe verifier: a non-`https` issuer, no
103
+ * algorithm or an unsupported one, no required scope, or no allowlisted subject or client. The
104
+ * last is the single-tenant rule: without it any holder of a token from the issuer is admitted.
105
+ */
106
+ declare function createAccessTokenVerifier(config: IAccessTokenVerifierConfig, deps?: IAccessTokenVerifierDeps): IAccessTokenVerifier;
107
+ //#endregion
108
+ export { type IAccessTokenVerifierDeps, type IIntegrityVerdict, type TIntegrityFailure, bearerCredential, createAccessTokenVerifier, credentialMatches, mintTransportToken, resolveAdmission, sealHandoffRecord, verifyHandoffPayload };
109
+ //# sourceMappingURL=node.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.cts","names":[],"sources":["../../src/node/admission.ts","../../src/node/handoff-manifest.ts","../../src/node/access-token-verifier.ts"],"mappings":";;;;;;;;;;;iBA4BgB;;;;;;;;;;;;;;;;;;;;;;;;iBA+BA,iBACd,SAAQ,4BAA4B,sBACnC;;;;;;;;;;;;;;;;;;;iBAoDa,kBAAkB,kBAAkB;iBA8BpC,iBAAiB;;;;;;;;;;;iBCpHjB,kBAAkB,QAAQ;EACxC;EACA,WAAW;;;KAcD;UAEK;WACN;WACA,UAAU;;WAEV;WACA;;;;;;;;;iBAUK,qBACd,oBACA,WAAW,oBACV;;;;UCCc;WACN,QAAQ;WACR,SAAS;;WAET;;WAEA;;;;;;;;;iBAwBK,0BACd,QAAQ,4BACR,OAAM,2BACL"}