@robota-sdk/agent-transport 3.0.0-beta.65 → 3.0.0-beta.67

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 (143) hide show
  1. package/README.md +97 -0
  2. package/dist/node/chunk-Bmb41Sf3.cjs +1 -0
  3. package/dist/node/headless/index.cjs +1 -1
  4. package/dist/node/headless/index.d.ts +2 -2
  5. package/dist/node/headless/index.js +1 -1
  6. package/dist/node/headless-B8yWkXW_.js +15 -0
  7. package/dist/node/headless-B8yWkXW_.js.map +1 -0
  8. package/dist/node/headless-Dp3kQVmo.cjs +14 -0
  9. package/dist/node/http/index.cjs +1 -1
  10. package/dist/node/http/index.d.ts +1 -1
  11. package/dist/node/http/index.js +1 -1
  12. package/dist/node/{http-DwO1AHG-.js → http-Br10Ps8m.js} +1 -1
  13. package/dist/node/http-Br10Ps8m.js.map +1 -0
  14. package/dist/node/http-Da6Kw4oy.cjs +1 -0
  15. package/dist/node/{index-B_rcr14p.d.ts → index-C5KNEBO9.d.ts} +2 -2
  16. package/dist/node/{index-B_rcr14p.d.ts.map → index-C5KNEBO9.d.ts.map} +1 -1
  17. package/dist/node/{index-Y0zHb1Bz.d.ts → index-C7DvsmEg.d.ts} +2 -2
  18. package/dist/node/{index-Y0zHb1Bz.d.ts.map → index-C7DvsmEg.d.ts.map} +1 -1
  19. package/dist/node/index-C9LWCL4l.d.ts.map +1 -1
  20. package/dist/node/{index-k3TUjA-T.d.ts → index-CP7kaYMg.d.ts} +17 -2
  21. package/dist/node/index-CP7kaYMg.d.ts.map +1 -0
  22. package/dist/node/index-CQsMNXAh.d.ts +67 -0
  23. package/dist/node/index-CQsMNXAh.d.ts.map +1 -0
  24. package/dist/node/{index-nBlMTFkZ.d.ts → index-DOA2KIYt.d.ts} +2 -2
  25. package/dist/node/{index-nBlMTFkZ.d.ts.map → index-DOA2KIYt.d.ts.map} +1 -1
  26. package/dist/node/{index-D34WUfFH.d.ts → index-Gby9H4q2.d.ts} +17 -2
  27. package/dist/node/index-Gby9H4q2.d.ts.map +1 -0
  28. package/dist/node/{index-CvXLpjJO.d.ts → index-X2Zg8FEY.d.ts} +2 -2
  29. package/dist/node/{index-CEs25wVk.d.ts.map → index-X2Zg8FEY.d.ts.map} +1 -1
  30. package/dist/node/index-rGmGTQ9o.d.ts +67 -0
  31. package/dist/node/index-rGmGTQ9o.d.ts.map +1 -0
  32. package/dist/node/{index-CEs25wVk.d.ts → index-yvGShbDx.d.ts} +2 -2
  33. package/dist/node/{index-CvXLpjJO.d.ts.map → index-yvGShbDx.d.ts.map} +1 -1
  34. package/dist/node/index.cjs +1 -1
  35. package/dist/node/index.d.ts +28 -6
  36. package/dist/node/index.d.ts.map +1 -0
  37. package/dist/node/index.js +2 -1
  38. package/dist/node/index.js.map +1 -0
  39. package/dist/node/mcp/index.cjs +1 -1
  40. package/dist/node/mcp/index.d.ts +1 -1
  41. package/dist/node/mcp/index.js +1 -1
  42. package/dist/node/{mcp-BXBwF6Wu.js → mcp-BAujHOMr.js} +1 -1
  43. package/dist/node/mcp-BAujHOMr.js.map +1 -0
  44. package/dist/node/mcp-Bl8jUfev.cjs +1 -0
  45. package/dist/node/tui/index.cjs +1 -1
  46. package/dist/node/tui/index.d.ts +2 -2
  47. package/dist/node/tui/index.js +1 -1
  48. package/dist/node/tui-B8G3yHrL.cjs +24 -0
  49. package/dist/node/tui-DUIfVw3G.js +25 -0
  50. package/dist/node/tui-DUIfVw3G.js.map +1 -0
  51. package/dist/node/ws/index.cjs +1 -1
  52. package/dist/node/ws/index.d.ts +1 -1
  53. package/dist/node/ws/index.js +1 -1
  54. package/dist/node/{ws-B-oRccFl.js → ws-BWel8nzl.js} +1 -1
  55. package/dist/node/ws-BWel8nzl.js.map +1 -0
  56. package/dist/node/ws-tCjj2gPu.cjs +1 -0
  57. package/package.json +19 -18
  58. package/src/headless/cli-input.ts +50 -0
  59. package/src/headless/headless-runner.ts +2 -1
  60. package/src/headless/headless-stream-json.ts +1 -0
  61. package/src/headless/headless-transport.ts +3 -2
  62. package/src/headless/index.ts +2 -0
  63. package/src/headless/print-terminal.ts +57 -0
  64. package/src/http/http-transport.ts +3 -3
  65. package/src/http/routes.ts +2 -1
  66. package/src/index.ts +2 -1
  67. package/src/mcp/mcp-server.ts +1 -0
  68. package/src/mcp/mcp-transport.ts +3 -2
  69. package/src/transport-registry.ts +100 -0
  70. package/src/tui/App.tsx +52 -34
  71. package/src/tui/BackgroundTaskPanel.tsx +4 -2
  72. package/src/tui/CjkTextInput.tsx +3 -2
  73. package/src/tui/ConfirmPrompt.tsx +2 -1
  74. package/src/tui/ExecutionWorkspaceDetailPane.tsx +7 -5
  75. package/src/tui/ExecutionWorkspaceSwitcher.tsx +8 -6
  76. package/src/tui/InputArea.tsx +20 -19
  77. package/src/tui/InteractivePrompt.tsx +5 -3
  78. package/src/tui/ListPicker.tsx +2 -1
  79. package/src/tui/MenuSelect.tsx +2 -1
  80. package/src/tui/MessageList.tsx +8 -6
  81. package/src/tui/PermissionPrompt.tsx +5 -3
  82. package/src/tui/PluginTUI.tsx +5 -3
  83. package/src/tui/SessionPicker.tsx +4 -2
  84. package/src/tui/SessionStatusBar.tsx +7 -3
  85. package/src/tui/SlashAutocomplete.tsx +4 -3
  86. package/src/tui/StatusBar.tsx +5 -3
  87. package/src/tui/StreamingIndicator.tsx +4 -2
  88. package/src/tui/TextPrompt.tsx +2 -1
  89. package/src/tui/ToolCommandOutput.tsx +4 -2
  90. package/src/tui/ToolDiffBlock.tsx +4 -2
  91. package/src/tui/TransportTUI.tsx +3 -2
  92. package/src/tui/UpdateNotice.tsx +1 -1
  93. package/src/tui/UsageSummaryEntry.tsx +3 -2
  94. package/src/tui/WaveText.tsx +1 -1
  95. package/src/tui/__tests__/fixtures/provider-setup-prompt-driver.tsx +7 -4
  96. package/src/tui/background-task-row-format.ts +2 -1
  97. package/src/tui/command-interaction-registry.ts +66 -0
  98. package/src/tui/command-interaction.ts +9 -35
  99. package/src/tui/create-default-tui-cli-adapter.ts +42 -0
  100. package/src/tui/flows/input-area-flow.ts +3 -2
  101. package/src/tui/hooks/command-effect-handler.ts +4 -3
  102. package/src/tui/hooks/side-effects-types.ts +1 -1
  103. package/src/tui/hooks/use-interactive-session-init.ts +4 -2
  104. package/src/tui/hooks/useAutocomplete.ts +1 -0
  105. package/src/tui/hooks/useInteractiveSession.ts +22 -19
  106. package/src/tui/hooks/usePermissionQueue.ts +2 -1
  107. package/src/tui/hooks/usePluginCallbacks.ts +1 -0
  108. package/src/tui/hooks/usePluginScreenData.ts +2 -1
  109. package/src/tui/hooks/useSideEffects.ts +8 -6
  110. package/src/tui/hooks/useSlashRouting.ts +4 -3
  111. package/src/tui/hooks/useStatusLineSettings.ts +4 -2
  112. package/src/tui/index.ts +3 -1
  113. package/src/tui/interactions/CommandConfirm.tsx +2 -1
  114. package/src/tui/interactions/CommandPicker.tsx +2 -1
  115. package/src/tui/render-markdown.ts +2 -1
  116. package/src/tui/render.tsx +12 -28
  117. package/src/tui/tui-cli-adapter-context.tsx +1 -0
  118. package/src/tui/tui-cli-adapter.ts +1 -1
  119. package/src/tui/tui-transport.ts +5 -4
  120. package/src/tui/utils/edit-diff.ts +1 -0
  121. package/src/tui/utils/tool-call-extractor.ts +1 -0
  122. package/src/ws/ws-background-messages.ts +1 -1
  123. package/src/ws/ws-handler.ts +6 -5
  124. package/src/ws/ws-transport-configurable.ts +6 -3
  125. package/src/ws/ws-transport.ts +3 -2
  126. package/dist/node/headless-CWEpJXFK.js +0 -7
  127. package/dist/node/headless-CWEpJXFK.js.map +0 -1
  128. package/dist/node/headless-DJ5pnxM6.cjs +0 -6
  129. package/dist/node/http-CuQE6V6t.cjs +0 -1
  130. package/dist/node/http-DwO1AHG-.js.map +0 -1
  131. package/dist/node/index-CSgNoyPK.d.ts +0 -85
  132. package/dist/node/index-CSgNoyPK.d.ts.map +0 -1
  133. package/dist/node/index-D34WUfFH.d.ts.map +0 -1
  134. package/dist/node/index-_dNm-2J3.d.ts +0 -85
  135. package/dist/node/index-_dNm-2J3.d.ts.map +0 -1
  136. package/dist/node/index-k3TUjA-T.d.ts.map +0 -1
  137. package/dist/node/mcp-BXBwF6Wu.js.map +0 -1
  138. package/dist/node/mcp-BiJsIywJ.cjs +0 -1
  139. package/dist/node/tui-BIpIcT7-.cjs +0 -24
  140. package/dist/node/tui-DBLn1T15.js +0 -25
  141. package/dist/node/tui-DBLn1T15.js.map +0 -1
  142. package/dist/node/ws-B-oRccFl.js.map +0 -1
  143. package/dist/node/ws-XRTSFZOK.cjs +0 -1
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../ws-XRTSFZOK.cjs`);exports.WsTransport=e.t,exports.createWsHandler=e.r,exports.createWsTransport=e.n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../ws-tCjj2gPu.cjs`);exports.WsTransport=e.t,exports.createWsHandler=e.r,exports.createWsTransport=e.n;
@@ -1,2 +1,2 @@
1
- import { a as IWsTransportConfig, c as createWsTransport, d as TClientMessage, f as TServerMessage, i as TExecutionWorkspaceStatus, l as IWsHandlerOptions, n as IExecutionWorkspaceSnapshot, o as WsTransport, r as TExecutionAttention, s as IWsTransportOptions, t as IExecutionWorkspaceEntry, u as createWsHandler } from "../index-CvXLpjJO.js";
1
+ import { a as IWsTransportConfig, c as createWsTransport, d as TClientMessage, f as TServerMessage, i as TExecutionWorkspaceStatus, l as IWsHandlerOptions, n as IExecutionWorkspaceSnapshot, o as WsTransport, r as TExecutionAttention, s as IWsTransportOptions, t as IExecutionWorkspaceEntry, u as createWsHandler } from "../index-yvGShbDx.js";
2
2
  export { type IExecutionWorkspaceEntry, type IExecutionWorkspaceSnapshot, type IWsHandlerOptions, type IWsTransportConfig, type IWsTransportOptions, type TClientMessage, type TExecutionAttention, type TExecutionWorkspaceStatus, type TServerMessage, WsTransport, createWsHandler, createWsTransport };
@@ -1 +1 @@
1
- import{n as e,r as t,t as n}from"../ws-B-oRccFl.js";export{n as WsTransport,t as createWsHandler,e as createWsTransport};
1
+ import{n as e,r as t,t as n}from"../ws-BWel8nzl.js";export{n as WsTransport,t as createWsHandler,e as createWsTransport};
@@ -1,2 +1,2 @@
1
1
  import{createServer as e}from"node:http";import{WebSocket as t,WebSocketServer as n}from"ws";function r(e,t,n){if(n.type===`get-background-tasks`){t({type:`background_tasks`,tasks:e.listBackgroundTasks(n.filter)});return}if(n.type===`get-background-task`){a(e,t,n);return}if(n.type===`get-background-job-groups`){t({type:`background_job_groups`,groups:e.listBackgroundJobGroups()});return}if(n.type===`get-background-job-group`){s(e,t,n);return}if(n.type===`wait-background-job-group`){c(e,t,n);return}o(e,t,n)}function i(e,t,n){if(!n.taskId){t({type:`protocol_error`,message:`taskId is required`});return}if(n.type===`cancel-background-task`){u(t,`cancel`,n.taskId,e.cancelBackgroundTask(n.taskId,n.reason));return}if(n.type===`close-background-task`){u(t,`close`,n.taskId,e.closeBackgroundTask(n.taskId));return}l(e,t,n)}function a(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 o(e,t,n){if(!n.taskId){t({type:`protocol_error`,message:`taskId is required`});return}e.readBackgroundTaskLog(n.taskId,n.cursor).then(e=>t({type:`background_task_log`,taskId:n.taskId,page:e}),e=>t({type:`protocol_error`,message:e.message}))}function s(e,t,n){if(!n.groupId){t({type:`protocol_error`,message:`groupId is required`});return}t({type:`background_job_group`,groupId:n.groupId,group:e.getBackgroundJobGroup(n.groupId)??null})}function c(e,t,n){if(!n.groupId){t({type:`protocol_error`,message:`groupId is required`});return}e.waitBackgroundJobGroup(n.groupId).then(e=>t({type:`background_job_group`,groupId:n.groupId,group:e}),e=>t({type:`protocol_error`,message:e.message}))}function l(e,t,n){if(!n.input){t({type:`protocol_error`,message:`input is required`});return}u(t,`send`,n.taskId,e.sendBackgroundTask(n.taskId,n.input))}function u(e,t,n,r){r.then(()=>e({type:`background_task_control_result`,action:t,taskId:n,success:!0}),r=>e({type:`background_task_control_result`,action:t,taskId:n,success:!1,message:r.message}))}function d(e){let t=f(e.session,e.send);return{onMessage:p(e.session,e.send),cleanup:t}}function f(e,t){let n=e=>t({type:`user_message`,content:e}),r=e=>t({type:`text_delta`,delta:e}),i=e=>t({type:`tool_start`,state:e}),a=e=>t({type:`tool_end`,state:e}),o=e=>t({type:`thinking`,isThinking:e}),s=e=>t({type:`complete`,result:e}),c=e=>t({type:`interrupted`,result:e}),l=e=>t({type:`error`,message:e.message}),u=e=>t({type:`background_task_event`,event:e}),d=e=>t({type:`background_job_group_event`,event:e}),f=e=>t({type:`execution_workspace_event`,snapshot:e.snapshot});return e.on(`user_message`,n),e.on(`text_delta`,r),e.on(`tool_start`,i),e.on(`tool_end`,a),e.on(`thinking`,o),e.on(`complete`,s),e.on(`interrupted`,c),e.on(`error`,l),e.on(`background_task_event`,u),e.on(`background_job_group_event`,d),e.on(`execution_workspace_event`,f),()=>{e.off(`user_message`,n),e.off(`text_delta`,r),e.off(`tool_start`,i),e.off(`tool_end`,a),e.off(`thinking`,o),e.off(`complete`,s),e.off(`interrupted`,c),e.off(`error`,l),e.off(`background_task_event`,u),e.off(`background_job_group_event`,d),e.off(`execution_workspace_event`,f)}}function p(e,t){return n=>{let r=m(n,t);r&&h(e,t,r)}}function m(e,t){try{return JSON.parse(e)}catch{return t({type:`protocol_error`,message:`Invalid JSON`}),null}}function h(e,t,n){if(_(n)){x(e,t,n);return}if(v(n)){S(e,t,n);return}if(y(n)){r(e,t,n);return}if(b(n)){i(e,t,n);return}t({type:`protocol_error`,message:`Unknown message type: ${g(n)}`})}function g(e){return e.type}function _(e){return e.type===`submit`||e.type===`command`||e.type===`abort`||e.type===`cancel-queue`}function v(e){return e.type===`get-messages`||e.type===`get-context`||e.type===`get-executing`||e.type===`get-pending`||e.type===`get-execution-workspace`}function y(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 b(e){return e.type===`cancel-background-task`||e.type===`close-background-task`||e.type===`send-background-task`}function x(e,t,n){if(n.type===`submit`){if(!n.prompt){t({type:`protocol_error`,message:`prompt is required`});return}e.submit(n.prompt)}else if(n.type===`command`){if(!n.name){t({type:`protocol_error`,message:`name is required`});return}e.executeCommand(n.name,n.args??``).then(e=>{t({type:`command_result`,name:n.name,message:e?.message??`Unknown command: ${n.name}`,success:e?.success??!1,data:e?.data})})}else n.type===`abort`?e.abort():e.cancelQueue()}function S(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 C(e){let t=null,n=null;return{name:`ws`,onMessage:null,attach(e){t=e},async start(){if(!t)throw Error(`No session attached. Call attach() first.`);let r=d({session:t,send:e.send});n=r.cleanup,this.onMessage=r.onMessage},async stop(){n?.(),n=null,this.onMessage=null}}}const w=7070;var T=class{name=`ws`;defaultEnabled=!0;optionsSchema={port:{type:`number`,description:`WebSocket server port`,default:w},maxRetries:{type:`number`,description:`Port retry attempts when port is occupied`,default:20}};session=null;stopFn=null;port;maxRetries;constructor(e={}){this.port=e.port??w,this.maxRetries=e.maxRetries??20}attach(e){this.session=e}async start(){if(!this.session)throw Error(`WsTransport: attach() must be called before start()`);let e=await this.bindWithRetry(this.session,this.port,this.maxRetries);this.stopFn=e.stop}async stop(){await this.stopFn?.(),this.stopFn=null}validateOptions(e){let{port:t,maxRetries:n}=e;return!(t!==void 0&&(typeof t!=`number`||t<1||t>65535)||n!==void 0&&(typeof n!=`number`||n<0))}bindWithRetry(e,t,n){return this.tryBind(e,t).catch(r=>{if(r.code===`EADDRINUSE`&&n>0)return this.bindWithRetry(e,t+1,n-1);throw r})}tryBind(r,i){return new Promise((a,o)=>{let s=e((e,t)=>{t.writeHead(400).end(`WebSocket endpoint`)});s.on(`error`,e=>{s.close(),o(e)}),s.listen(i,`127.0.0.1`,()=>{let e=new n({server:s});e.on(`connection`,e=>{let n=n=>{e.readyState===t.OPEN&&e.send(JSON.stringify(n))},{onMessage:i,cleanup:a}=d({session:r,send:n});e.on(`message`,e=>i(String(e))),e.on(`close`,a),e.on(`error`,a),n({type:`messages`,messages:r.getMessages()}),n({type:`execution_workspace_event`,snapshot:r.getExecutionWorkspaceSnapshot()})}),a({stop:()=>new Promise(t=>{e.close(()=>s.close(()=>t()))})})})})}};export{C as n,d as r,T as t};
2
- //# sourceMappingURL=ws-B-oRccFl.js.map
2
+ //# sourceMappingURL=ws-BWel8nzl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-BWel8nzl.js","names":[],"sources":["../../src/ws/ws-background-messages.ts","../../src/ws/ws-handler.ts","../../src/ws/ws-transport.ts","../../src/ws/ws-transport-configurable.ts"],"sourcesContent":["import type { TBackgroundControlAction, TClientMessage, TServerMessage } from './ws-protocol.js';\nimport type { IInteractiveSession } from '@robota-sdk/agent-framework';\n\nexport function handleBackgroundQueryMessage(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\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'\n | 'get-background-job-group'\n | 'wait-background-job-group';\n }\n >,\n): void {\n if (msg.type === 'get-background-tasks') {\n send({ type: 'background_tasks', tasks: session.listBackgroundTasks(msg.filter) });\n return;\n }\n if (msg.type === 'get-background-task') {\n sendBackgroundTaskSnapshot(session, send, msg);\n return;\n }\n if (msg.type === 'get-background-job-groups') {\n send({ type: 'background_job_groups', groups: session.listBackgroundJobGroups() });\n return;\n }\n if (msg.type === 'get-background-job-group') {\n sendBackgroundJobGroupSnapshot(session, send, msg);\n return;\n }\n if (msg.type === 'wait-background-job-group') {\n sendBackgroundJobGroupWaitResult(session, send, msg);\n return;\n }\n sendBackgroundTaskLogPage(session, send, msg);\n}\n\nexport function handleBackgroundControlMessage(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n msg: Extract<\n TClientMessage,\n { type: 'cancel-background-task' | 'close-background-task' | 'send-background-task' }\n >,\n): void {\n if (!msg.taskId) {\n send({ type: 'protocol_error', message: 'taskId is required' });\n return;\n }\n if (msg.type === 'cancel-background-task') {\n sendBackgroundTaskControlResult(\n send,\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 send,\n 'close',\n msg.taskId,\n session.closeBackgroundTask(msg.taskId),\n );\n return;\n }\n sendBackgroundTaskInput(session, send, msg);\n}\n\nfunction sendBackgroundTaskSnapshot(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n msg: Extract<TClientMessage, { type: 'get-background-task' }>,\n): void {\n if (!msg.taskId) {\n send({ type: 'protocol_error', message: 'taskId is required' });\n return;\n }\n send({\n type: 'background_task',\n taskId: msg.taskId,\n task: session.getBackgroundTask(msg.taskId) ?? null,\n });\n}\n\nfunction sendBackgroundTaskLogPage(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n msg: Extract<TClientMessage, { type: 'read-background-task-log' }>,\n): void {\n if (!msg.taskId) {\n send({ type: 'protocol_error', message: 'taskId is required' });\n return;\n }\n session.readBackgroundTaskLog(msg.taskId, msg.cursor).then(\n (page) => send({ type: 'background_task_log', taskId: msg.taskId, page }),\n (error: Error) => send({ type: 'protocol_error', message: error.message }),\n );\n}\n\nfunction sendBackgroundJobGroupSnapshot(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n msg: Extract<TClientMessage, { type: 'get-background-job-group' }>,\n): void {\n if (!msg.groupId) {\n send({ type: 'protocol_error', message: 'groupId is required' });\n return;\n }\n send({\n type: 'background_job_group',\n groupId: msg.groupId,\n group: session.getBackgroundJobGroup(msg.groupId) ?? null,\n });\n}\n\nfunction sendBackgroundJobGroupWaitResult(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n msg: Extract<TClientMessage, { type: 'wait-background-job-group' }>,\n): void {\n if (!msg.groupId) {\n send({ type: 'protocol_error', message: 'groupId is required' });\n return;\n }\n session.waitBackgroundJobGroup(msg.groupId).then(\n (group) => send({ type: 'background_job_group', groupId: msg.groupId, group }),\n (error: Error) => send({ type: 'protocol_error', message: error.message }),\n );\n}\n\nfunction sendBackgroundTaskInput(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n msg: Extract<TClientMessage, { type: 'send-background-task' }>,\n): void {\n if (!msg.input) {\n send({ type: 'protocol_error', message: 'input is required' });\n return;\n }\n sendBackgroundTaskControlResult(\n send,\n 'send',\n msg.taskId,\n session.sendBackgroundTask(msg.taskId, msg.input),\n );\n}\n\nfunction sendBackgroundTaskControlResult(\n send: (message: TServerMessage) => void,\n action: TBackgroundControlAction,\n taskId: string,\n operation: Promise<void>,\n): void {\n operation.then(\n () => send({ type: 'background_task_control_result', action, taskId, success: true }),\n (error: Error) =>\n send({\n type: 'background_task_control_result',\n action,\n taskId,\n success: false,\n message: error.message,\n }),\n );\n}\n","/**\n * WebSocket transport adapter — exposes IInteractiveSession over WebSocket.\n *\n * Framework-agnostic: works with any WebSocket implementation via\n * send/onMessage callbacks. No dependency on ws, uWebSockets, etc.\n *\n * Protocol: JSON messages with { type, ...payload } structure.\n * Server pushes IInteractiveSession events to client in real-time.\n */\n\nimport {\n handleBackgroundControlMessage,\n handleBackgroundQueryMessage,\n} from './ws-background-messages.js';\n\nimport type { TClientMessage, TServerMessage } from './ws-protocol.js';\nimport type {\n IInteractiveSession,\n IExecutionResult,\n IExecutionWorkspaceEvent,\n TBackgroundJobGroupEvent,\n TBackgroundTaskEvent,\n IToolState,\n} from '@robota-sdk/agent-framework';\n\nexport interface IWsHandlerOptions {\n /** IInteractiveSession to expose. */\n session: IInteractiveSession;\n /** Send a JSON message to the client. */\n send: (message: TServerMessage) => void;\n}\n\n/**\n * Create a WebSocket message handler for an IInteractiveSession.\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 { onMessage, cleanup } = createWsHandler({\n * session: interactiveSession,\n * send: (msg) => ws.send(JSON.stringify(msg)),\n * });\n *\n * ws.on('message', (data) => onMessage(String(data)));\n * ws.on('close', cleanup);\n * ```\n */\nexport function createWsHandler(options: IWsHandlerOptions): {\n onMessage: (data: string) => void;\n cleanup: () => void;\n} {\n const cleanup = subscribeSessionEvents(options.session, options.send);\n const onMessage = createWsMessageHandler(options.session, options.send);\n\n return { onMessage, cleanup };\n}\n\nfunction subscribeSessionEvents(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n): () => void {\n const onUserMessage = (content: string): void => send({ type: 'user_message', content });\n const onTextDelta = (delta: string): void => send({ type: 'text_delta', delta });\n const onToolStart = (state: IToolState): void => send({ type: 'tool_start', state });\n const onToolEnd = (state: IToolState): void => send({ type: 'tool_end', state });\n const onThinking = (isThinking: boolean): void => send({ type: 'thinking', isThinking });\n const onComplete = (result: IExecutionResult): void => send({ type: 'complete', result });\n const onInterrupted = (result: IExecutionResult): void => send({ type: 'interrupted', result });\n const onError = (error: Error): void => send({ type: 'error', message: error.message });\n const onBackgroundTaskEvent = (event: TBackgroundTaskEvent): void =>\n send({ type: 'background_task_event', event });\n const onBackgroundJobGroupEvent = (event: TBackgroundJobGroupEvent): void =>\n send({ type: 'background_job_group_event', event });\n const onExecutionWorkspace = (event: IExecutionWorkspaceEvent): void =>\n send({ type: 'execution_workspace_event', snapshot: event.snapshot });\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\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 };\n}\n\nfunction createWsMessageHandler(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n): (data: string) => void {\n return (data: string): void => {\n const msg = parseClientMessage(data, send);\n if (!msg) return;\n handleClientMessage(session, send, msg);\n };\n}\n\nfunction parseClientMessage(\n data: string,\n send: (message: TServerMessage) => void,\n): TClientMessage | null {\n try {\n return JSON.parse(data) as TClientMessage;\n } catch {\n send({ type: 'protocol_error', message: 'Invalid JSON' });\n return null;\n }\n}\n\nfunction handleClientMessage(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n msg: TClientMessage,\n): void {\n if (isSessionControlMessage(msg)) {\n handleSessionControlMessage(session, send, msg);\n return;\n }\n if (isSessionQueryMessage(msg)) {\n handleSessionQueryMessage(session, send, msg);\n return;\n }\n if (isBackgroundQueryMessage(msg)) {\n handleBackgroundQueryMessage(session, send, msg);\n return;\n }\n if (isBackgroundControlMessage(msg)) {\n handleBackgroundControlMessage(session, send, msg);\n return;\n }\n send({ type: 'protocol_error', message: `Unknown message type: ${getMessageType(msg)}` });\n}\n\nfunction getMessageType(msg: TClientMessage): string {\n return (msg as { type: string }).type;\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 isSessionQueryMessage(msg: TClientMessage): msg is Extract<\n TClientMessage,\n {\n type:\n | 'get-messages'\n | 'get-context'\n | 'get-executing'\n | 'get-pending'\n | 'get-execution-workspace';\n }\n> {\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\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 handleSessionControlMessage(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n msg: Extract<TClientMessage, { type: 'submit' | 'command' | 'abort' | 'cancel-queue' }>,\n): void {\n if (msg.type === 'submit') {\n if (!msg.prompt) {\n send({ type: 'protocol_error', message: 'prompt is required' });\n return;\n }\n session.submit(msg.prompt);\n } else if (msg.type === 'command') {\n if (!msg.name) {\n send({ type: 'protocol_error', message: 'name is required' });\n return;\n }\n session.executeCommand(msg.name, msg.args ?? '').then((result) => {\n send({\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 } else if (msg.type === 'abort') {\n session.abort();\n } else {\n session.cancelQueue();\n }\n}\n\nfunction handleSessionQueryMessage(\n session: IInteractiveSession,\n send: (message: TServerMessage) => void,\n msg: Extract<\n TClientMessage,\n {\n type:\n | 'get-messages'\n | 'get-context'\n | 'get-executing'\n | 'get-pending'\n | 'get-execution-workspace';\n }\n >,\n): void {\n if (msg.type === 'get-messages') {\n send({ type: 'messages', messages: session.getMessages() });\n } else if (msg.type === 'get-context') {\n send({ type: 'context', state: session.getContextState() });\n } else if (msg.type === 'get-executing') {\n send({ type: 'executing', executing: session.isExecuting() });\n } else if (msg.type === 'get-execution-workspace') {\n send({\n type: 'execution_workspace_event',\n snapshot: session.getExecutionWorkspaceSnapshot(),\n });\n } else {\n send({ type: 'pending', pending: session.getPendingPrompt() });\n }\n}\n","/**\n * ITransportAdapter implementation for WebSocket transport.\n *\n * Wraps createWsHandler into the unified ITransportAdapter interface.\n * After start(), the consumer must wire onMessage to their WebSocket.\n */\n\nimport { createWsHandler } from './ws-handler.js';\n\nimport type { TServerMessage } from './ws-protocol.js';\nimport type { IInteractiveSession } from '@robota-sdk/agent-framework';\nimport type { ITransportAdapter } from '@robota-sdk/agent-interface-transport';\n\nexport interface IWsTransportOptions {\n /** Send a JSON message to the connected WebSocket client. */\n send: (message: TServerMessage) => void;\n}\n\nexport function createWsTransport(\n options: IWsTransportOptions,\n): ITransportAdapter<IInteractiveSession> & { onMessage: ((data: string) => void) | null } {\n let session: IInteractiveSession | null = null;\n let cleanup: (() => void) | null = null;\n\n return {\n name: 'ws',\n onMessage: null,\n attach(s: IInteractiveSession) {\n session = s;\n },\n async start() {\n if (!session) throw new Error('No session attached. Call attach() first.');\n const handler = createWsHandler({ session, send: options.send });\n cleanup = handler.cleanup;\n this.onMessage = handler.onMessage;\n },\n async stop() {\n cleanup?.();\n cleanup = null;\n this.onMessage = null;\n },\n };\n}\n","/**\n * Self-contained WS transport implementing IConfigurableTransport.\n * Owns the WebSocket server lifecycle (ws package), started/stopped via the transport registry.\n */\n\nimport { createServer, type Server } from 'node:http';\n\nimport { WebSocketServer, WebSocket } from 'ws';\n\nimport { createWsHandler } from './ws-handler.js';\n\nimport type { TServerMessage } from './ws-protocol.js';\nimport type { TUniversalValue } from '@robota-sdk/agent-core';\nimport type { IInteractiveSession } from '@robota-sdk/agent-framework';\nimport type { IConfigurableTransport } from '@robota-sdk/agent-interface-transport';\n\nconst DEFAULT_PORT = 7070;\nconst DEFAULT_MAX_RETRIES = 20;\n\nexport interface IWsTransportConfig {\n port?: number;\n maxRetries?: number;\n}\n\nexport class WsTransport implements IConfigurableTransport<IInteractiveSession> {\n readonly name = 'ws';\n readonly defaultEnabled = true;\n readonly optionsSchema = {\n port: { type: 'number', description: 'WebSocket server port', default: DEFAULT_PORT },\n maxRetries: {\n type: 'number',\n description: 'Port retry attempts when port is occupied',\n default: DEFAULT_MAX_RETRIES,\n },\n };\n\n private session: IInteractiveSession | null = null;\n private stopFn: (() => Promise<void>) | null = null;\n private readonly port: number;\n private readonly maxRetries: number;\n\n constructor(config: IWsTransportConfig = {}) {\n this.port = config.port ?? DEFAULT_PORT;\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n }\n\n attach(session: IInteractiveSession): void {\n this.session = session;\n }\n\n async start(): Promise<void> {\n if (!this.session) throw new Error('WsTransport: attach() must be called before start()');\n const handle = await this.bindWithRetry(this.session, this.port, this.maxRetries);\n this.stopFn = handle.stop;\n }\n\n async stop(): Promise<void> {\n await this.stopFn?.();\n this.stopFn = null;\n }\n\n validateOptions(options: Record<string, TUniversalValue>): boolean {\n const { port, maxRetries } = options;\n if (port !== undefined && (typeof port !== 'number' || port < 1 || port > 65535)) return false;\n if (maxRetries !== undefined && (typeof maxRetries !== 'number' || maxRetries < 0))\n return false;\n return true;\n }\n\n private bindWithRetry(\n session: IInteractiveSession,\n port: number,\n retriesLeft: number,\n ): Promise<{ stop: () => Promise<void> }> {\n return this.tryBind(session, port).catch((err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE' && retriesLeft > 0)\n return this.bindWithRetry(session, port + 1, retriesLeft - 1);\n throw err;\n });\n }\n\n private tryBind(\n session: IInteractiveSession,\n port: number,\n ): Promise<{ stop: () => Promise<void> }> {\n return new Promise((resolve, reject) => {\n const httpServer: Server = createServer((_, res) => {\n res.writeHead(400).end('WebSocket endpoint');\n });\n\n httpServer.on('error', (err: NodeJS.ErrnoException) => {\n httpServer.close();\n reject(err);\n });\n\n httpServer.listen(port, '127.0.0.1', () => {\n const wss = new WebSocketServer({ server: httpServer });\n\n wss.on('connection', (ws: WebSocket) => {\n const send = (message: TServerMessage): void => {\n if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(message));\n };\n\n const { onMessage, cleanup } = createWsHandler({ session, send });\n\n ws.on('message', (data) => onMessage(String(data)));\n ws.on('close', cleanup);\n ws.on('error', cleanup);\n\n send({ type: 'messages', messages: session.getMessages() });\n send({\n type: 'execution_workspace_event',\n snapshot: session.getExecutionWorkspaceSnapshot(),\n });\n });\n\n resolve({\n stop: () =>\n new Promise<void>((res) => {\n wss.close(() => httpServer.close(() => res()));\n }),\n });\n });\n });\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ require(`./chunk-Bmb41Sf3.cjs`);let e=require(`node:http`),t=require(`ws`);function n(e,t,n){if(n.type===`get-background-tasks`){t({type:`background_tasks`,tasks:e.listBackgroundTasks(n.filter)});return}if(n.type===`get-background-task`){i(e,t,n);return}if(n.type===`get-background-job-groups`){t({type:`background_job_groups`,groups:e.listBackgroundJobGroups()});return}if(n.type===`get-background-job-group`){o(e,t,n);return}if(n.type===`wait-background-job-group`){s(e,t,n);return}a(e,t,n)}function r(e,t,n){if(!n.taskId){t({type:`protocol_error`,message:`taskId is required`});return}if(n.type===`cancel-background-task`){l(t,`cancel`,n.taskId,e.cancelBackgroundTask(n.taskId,n.reason));return}if(n.type===`close-background-task`){l(t,`close`,n.taskId,e.closeBackgroundTask(n.taskId));return}c(e,t,n)}function i(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 a(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 o(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 s(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 c(e,t,n){if(!n.input){t({type:`protocol_error`,message:`input is required`});return}l(t,`send`,n.taskId,e.sendBackgroundTask(n.taskId,n.input))}function l(e,t,n,r){r.then(()=>e({type:`background_task_control_result`,action:t,taskId:n,success:!0}),r=>e({type:`background_task_control_result`,action:t,taskId:n,success:!1,message:r.message}))}function u(e){let t=d(e.session,e.send);return{onMessage:f(e.session,e.send),cleanup:t}}function d(e,t){let n=e=>t({type:`user_message`,content:e}),r=e=>t({type:`text_delta`,delta:e}),i=e=>t({type:`tool_start`,state:e}),a=e=>t({type:`tool_end`,state:e}),o=e=>t({type:`thinking`,isThinking:e}),s=e=>t({type:`complete`,result:e}),c=e=>t({type:`interrupted`,result:e}),l=e=>t({type:`error`,message:e.message}),u=e=>t({type:`background_task_event`,event:e}),d=e=>t({type:`background_job_group_event`,event:e}),f=e=>t({type:`execution_workspace_event`,snapshot:e.snapshot});return e.on(`user_message`,n),e.on(`text_delta`,r),e.on(`tool_start`,i),e.on(`tool_end`,a),e.on(`thinking`,o),e.on(`complete`,s),e.on(`interrupted`,c),e.on(`error`,l),e.on(`background_task_event`,u),e.on(`background_job_group_event`,d),e.on(`execution_workspace_event`,f),()=>{e.off(`user_message`,n),e.off(`text_delta`,r),e.off(`tool_start`,i),e.off(`tool_end`,a),e.off(`thinking`,o),e.off(`complete`,s),e.off(`interrupted`,c),e.off(`error`,l),e.off(`background_task_event`,u),e.off(`background_job_group_event`,d),e.off(`execution_workspace_event`,f)}}function f(e,t){return n=>{let r=p(n,t);r&&m(e,t,r)}}function p(e,t){try{return JSON.parse(e)}catch{return t({type:`protocol_error`,message:`Invalid JSON`}),null}}function m(e,t,i){if(g(i)){b(e,t,i);return}if(_(i)){x(e,t,i);return}if(v(i)){n(e,t,i);return}if(y(i)){r(e,t,i);return}t({type:`protocol_error`,message:`Unknown message type: ${h(i)}`})}function h(e){return e.type}function g(e){return e.type===`submit`||e.type===`command`||e.type===`abort`||e.type===`cancel-queue`}function _(e){return e.type===`get-messages`||e.type===`get-context`||e.type===`get-executing`||e.type===`get-pending`||e.type===`get-execution-workspace`}function v(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 y(e){return e.type===`cancel-background-task`||e.type===`close-background-task`||e.type===`send-background-task`}function b(e,t,n){if(n.type===`submit`){if(!n.prompt){t({type:`protocol_error`,message:`prompt is required`});return}e.submit(n.prompt)}else if(n.type===`command`){if(!n.name){t({type:`protocol_error`,message:`name is required`});return}e.executeCommand(n.name,n.args??``).then(e=>{t({type:`command_result`,name:n.name,message:e?.message??`Unknown command: ${n.name}`,success:e?.success??!1,data:e?.data})})}else n.type===`abort`?e.abort():e.cancelQueue()}function x(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 S(e){let t=null,n=null;return{name:`ws`,onMessage:null,attach(e){t=e},async start(){if(!t)throw Error(`No session attached. Call attach() first.`);let r=u({session:t,send:e.send});n=r.cleanup,this.onMessage=r.onMessage},async stop(){n?.(),n=null,this.onMessage=null}}}const C=7070;var w=class{name=`ws`;defaultEnabled=!0;optionsSchema={port:{type:`number`,description:`WebSocket server port`,default:C},maxRetries:{type:`number`,description:`Port retry attempts when port is occupied`,default:20}};session=null;stopFn=null;port;maxRetries;constructor(e={}){this.port=e.port??C,this.maxRetries=e.maxRetries??20}attach(e){this.session=e}async start(){if(!this.session)throw Error(`WsTransport: attach() must be called before start()`);let e=await this.bindWithRetry(this.session,this.port,this.maxRetries);this.stopFn=e.stop}async stop(){await this.stopFn?.(),this.stopFn=null}validateOptions(e){let{port:t,maxRetries:n}=e;return!(t!==void 0&&(typeof t!=`number`||t<1||t>65535)||n!==void 0&&(typeof n!=`number`||n<0))}bindWithRetry(e,t,n){return this.tryBind(e,t).catch(r=>{if(r.code===`EADDRINUSE`&&n>0)return this.bindWithRetry(e,t+1,n-1);throw r})}tryBind(n,r){return new Promise((i,a)=>{let o=(0,e.createServer)((e,t)=>{t.writeHead(400).end(`WebSocket endpoint`)});o.on(`error`,e=>{o.close(),a(e)}),o.listen(r,`127.0.0.1`,()=>{let e=new t.WebSocketServer({server:o});e.on(`connection`,e=>{let r=n=>{e.readyState===t.WebSocket.OPEN&&e.send(JSON.stringify(n))},{onMessage:i,cleanup:a}=u({session:n,send:r});e.on(`message`,e=>i(String(e))),e.on(`close`,a),e.on(`error`,a),r({type:`messages`,messages:n.getMessages()}),r({type:`execution_workspace_event`,snapshot:n.getExecutionWorkspaceSnapshot()})}),i({stop:()=>new Promise(t=>{e.close(()=>o.close(()=>t()))})})})})}};Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return w}});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-transport",
3
- "version": "3.0.0-beta.65",
4
- "description": "Consolidated transport package for Robota SDK — TUI, headless, HTTP, WebSocket, and MCP adapters",
3
+ "version": "3.0.0-beta.67",
4
+ "description": "Consolidated transport package for Robota SDK — headless, HTTP, WebSocket, and MCP adapters",
5
5
  "type": "module",
6
6
  "main": "dist/node/index.js",
7
7
  "types": "dist/node/index.d.ts",
@@ -18,18 +18,6 @@
18
18
  "require": "./dist/node/index.cjs"
19
19
  }
20
20
  },
21
- "./tui": {
22
- "types": "./dist/node/tui/index.d.ts",
23
- "source": "./src/tui/index.ts",
24
- "node": {
25
- "import": "./dist/node/tui/index.js",
26
- "require": "./dist/node/tui/index.cjs"
27
- },
28
- "default": {
29
- "import": "./dist/node/tui/index.js",
30
- "require": "./dist/node/tui/index.cjs"
31
- }
32
- },
33
21
  "./headless": {
34
22
  "types": "./dist/node/headless/index.d.ts",
35
23
  "source": "./src/headless/index.ts",
@@ -77,6 +65,18 @@
77
65
  "import": "./dist/node/mcp/index.js",
78
66
  "require": "./dist/node/mcp/index.cjs"
79
67
  }
68
+ },
69
+ "./tui": {
70
+ "types": "./dist/node/tui/index.d.ts",
71
+ "source": "./src/tui/index.ts",
72
+ "node": {
73
+ "import": "./dist/node/tui/index.js",
74
+ "require": "./dist/node/tui/index.cjs"
75
+ },
76
+ "default": {
77
+ "import": "./dist/node/tui/index.js",
78
+ "require": "./dist/node/tui/index.cjs"
79
+ }
80
80
  }
81
81
  },
82
82
  "repository": {
@@ -108,9 +108,10 @@
108
108
  "string-width": "^8.2.0",
109
109
  "ws": "^8.18.3",
110
110
  "zod": "^3.24.4",
111
- "@robota-sdk/agent-core": "3.0.0-beta.65",
112
- "@robota-sdk/agent-interface-transport": "3.0.0-beta.65",
113
- "@robota-sdk/agent-framework": "3.0.0-beta.65"
111
+ "@robota-sdk/agent-core": "3.0.0-beta.67",
112
+ "@robota-sdk/agent-framework": "3.0.0-beta.67",
113
+ "@robota-sdk/agent-interface-tui": "3.0.0-beta.67",
114
+ "@robota-sdk/agent-interface-transport": "3.0.0-beta.67"
114
115
  },
115
116
  "devDependencies": {
116
117
  "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
@@ -123,7 +124,7 @@
123
124
  "tsx": "^4.7.0",
124
125
  "typescript": "^5.3.3",
125
126
  "vitest": "^1.6.1",
126
- "@robota-sdk/agent-command": "3.0.0-beta.65"
127
+ "@robota-sdk/agent-command": "3.0.0-beta.67"
127
128
  },
128
129
  "license": "MIT",
129
130
  "publishConfig": {
@@ -0,0 +1,50 @@
1
+ const PRINTABLE_ASCII_START = 32;
2
+
3
+ export const promptInput = (label: string, masked = false): Promise<string> =>
4
+ new Promise<string>((resolve, reject) => {
5
+ process.stdout.write(label);
6
+ let input = '';
7
+ const stdin = process.stdin;
8
+ const wasRaw = stdin.isRaw;
9
+ if (!stdin.isTTY) {
10
+ reject(
11
+ new Error(
12
+ 'Cannot prompt for input: stdin is not a TTY.\n' +
13
+ 'Set your API key via environment variable instead:\n' +
14
+ ' ANTHROPIC_API_KEY=<key> robota\n' +
15
+ ' OPENAI_API_KEY=<key> robota',
16
+ ),
17
+ );
18
+ return;
19
+ }
20
+ stdin.setRawMode(true);
21
+ stdin.resume();
22
+ stdin.setEncoding('utf8');
23
+ const onData = (data: string): void => {
24
+ for (const ch of data) {
25
+ if (ch === '\r' || ch === '\n') {
26
+ stdin.removeListener('data', onData);
27
+ stdin.setRawMode(wasRaw ?? false);
28
+ stdin.pause();
29
+ process.stdout.write('\n');
30
+ resolve(input.trim());
31
+ return;
32
+ } else if (ch === '\x7f' || ch === '\b') {
33
+ if (input.length > 0) {
34
+ input = input.slice(0, -1);
35
+ process.stdout.write('\b \b');
36
+ }
37
+ } else if (ch === '\x03') {
38
+ stdin.removeListener('data', onData);
39
+ stdin.setRawMode(wasRaw ?? false);
40
+ stdin.pause();
41
+ process.stdout.write('\n');
42
+ process.exit(0);
43
+ } else if (ch.charCodeAt(0) >= PRINTABLE_ASCII_START) {
44
+ input += ch;
45
+ process.stdout.write(masked ? '*' : ch);
46
+ }
47
+ }
48
+ };
49
+ stdin.on('data', onData);
50
+ });
@@ -1,6 +1,7 @@
1
- import type { IInteractiveSession, IExecutionResult } from '@robota-sdk/agent-framework';
2
1
  import { executeSlashCommandIfPresent, subscribeStreamJsonEvents } from './headless-stream-json.js';
3
2
 
3
+ import type { IInteractiveSession, IExecutionResult } from '@robota-sdk/agent-framework';
4
+
4
5
  export type TOutputFormat = 'text' | 'json' | 'stream-json';
5
6
 
6
7
  export interface IHeadlessRunnerOptions {
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+
2
3
  import type {
3
4
  IInteractiveSession,
4
5
  IExecutionResult,
@@ -5,10 +5,11 @@
5
5
  * After start() completes, getExitCode() returns the runner's exit code.
6
6
  */
7
7
 
8
- import type { IInteractiveSession } from '@robota-sdk/agent-framework';
9
- import type { ITransportAdapter } from '@robota-sdk/agent-interface-transport';
10
8
  import { createHeadlessRunner } from './headless-runner.js';
9
+
11
10
  import type { TOutputFormat } from './headless-runner.js';
11
+ import type { IInteractiveSession } from '@robota-sdk/agent-framework';
12
+ import type { ITransportAdapter } from '@robota-sdk/agent-interface-transport';
12
13
 
13
14
  export interface IHeadlessTransportOptions {
14
15
  /** Output format: 'text', 'json', or 'stream-json'. */
@@ -1,3 +1,5 @@
1
+ export { PrintTerminal } from './print-terminal.js';
2
+ export { promptInput } from './cli-input.js';
1
3
  export { createHeadlessRunner } from './headless-runner.js';
2
4
  export type { IHeadlessRunnerOptions, TOutputFormat } from './headless-runner.js';
3
5
  export { createHeadlessTransport } from './headless-transport.js';
@@ -0,0 +1,57 @@
1
+ /**
2
+ * ITerminalOutput implementation for print mode (-p).
3
+ *
4
+ * Writes to stdout/stderr directly. The readline-based prompt and select are
5
+ * only invoked if the agent triggers a permission-gated tool, which is rare in
6
+ * one-shot print mode but must still work correctly.
7
+ */
8
+
9
+ import * as readline from 'node:readline';
10
+
11
+ import type { ITerminalOutput, ISpinner } from '@robota-sdk/agent-core';
12
+
13
+ export class PrintTerminal implements ITerminalOutput {
14
+ write(text: string): void {
15
+ process.stdout.write(text);
16
+ }
17
+ writeLine(text: string): void {
18
+ process.stdout.write(text + '\n');
19
+ }
20
+ writeMarkdown(md: string): void {
21
+ process.stdout.write(md);
22
+ }
23
+ writeError(text: string): void {
24
+ process.stderr.write(text + '\n');
25
+ }
26
+ prompt(question: string): Promise<string> {
27
+ return new Promise<string>((resolve) => {
28
+ const rl = readline.createInterface({
29
+ input: process.stdin,
30
+ output: process.stdout,
31
+ terminal: false,
32
+ historySize: 0,
33
+ });
34
+ rl.question(question, (answer) => {
35
+ rl.close();
36
+ resolve(answer);
37
+ });
38
+ });
39
+ }
40
+ async select(options: string[], initialIndex = 0): Promise<number> {
41
+ for (let i = 0; i < options.length; i++) {
42
+ const marker = i === initialIndex ? '>' : ' ';
43
+ process.stdout.write(` ${marker} ${i + 1}) ${options[i]}\n`);
44
+ }
45
+ const answer = await this.prompt(
46
+ ` Choose [1-${options.length}] (default: ${options[initialIndex]}): `,
47
+ );
48
+ const trimmed = answer.trim().toLowerCase();
49
+ if (trimmed === '') return initialIndex;
50
+ const num = parseInt(trimmed, 10);
51
+ if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
52
+ return initialIndex;
53
+ }
54
+ spinner(_message: string): ISpinner {
55
+ return { stop(): void {}, update(): void {} };
56
+ }
57
+ }
@@ -5,10 +5,10 @@
5
5
  * while exposing the underlying Hono app via getApp().
6
6
  */
7
7
 
8
- import type { ITransportAdapter } from '@robota-sdk/agent-interface-transport';
9
- import type { IInteractiveSession } from '@robota-sdk/agent-framework';
10
-
11
8
  import { createAgentRoutes } from './routes.js';
9
+
10
+ import type { IInteractiveSession } from '@robota-sdk/agent-framework';
11
+ import type { ITransportAdapter } from '@robota-sdk/agent-interface-transport';
12
12
  import type { Hono } from 'hono';
13
13
 
14
14
  export interface IHttpTransportOptions {
@@ -7,8 +7,9 @@
7
7
 
8
8
  import { Hono } from 'hono';
9
9
  import { streamSSE } from 'hono/streaming';
10
- import type { Context } from 'hono';
10
+
11
11
  import type { IInteractiveSession } from '@robota-sdk/agent-framework';
12
+ import type { Context } from 'hono';
12
13
 
13
14
  /** Callback that resolves an IInteractiveSession from the request context. */
14
15
  export type TSessionFactory = (c: Context) => IInteractiveSession | Promise<IInteractiveSession>;
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
- export * from './tui/index.js';
2
1
  export * from './headless/index.js';
3
2
  export * from './http/index.js';
4
3
  export * from './ws/index.js';
5
4
  export * from './mcp/index.js';
5
+ export * from './tui/index.js';
6
+ export { TransportRegistry, createDefaultTransportRegistry } from './transport-registry.js';
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
10
10
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
11
+
11
12
  import type { IInteractiveSession, IExecutionResult } from '@robota-sdk/agent-framework';
12
13
 
13
14
  export interface IAgentMcpOptions {
@@ -5,10 +5,11 @@
5
5
  * while exposing the underlying MCP Server via getServer().
6
6
  */
7
7
 
8
- import type { ITransportAdapter } from '@robota-sdk/agent-interface-transport';
9
- import type { IInteractiveSession } from '@robota-sdk/agent-framework';
10
8
  import { createAgentMcpServer } from './mcp-server.js';
9
+
11
10
  import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
+ import type { IInteractiveSession } from '@robota-sdk/agent-framework';
12
+ import type { ITransportAdapter } from '@robota-sdk/agent-interface-transport';
12
13
 
13
14
  export interface IMcpTransportOptions {
14
15
  /** Name for the MCP server. */
@@ -0,0 +1,100 @@
1
+ /**
2
+ * TransportRegistry — manages IConfigurableTransport instances with settings-backed enable/disable.
3
+ *
4
+ * Settings file shape (under `transports` key in settings.json):
5
+ * { "ws": { "enabled": true, "options": { "port": 7070 } } }
6
+ */
7
+
8
+ import { getUserSettingsPath } from '@robota-sdk/agent-framework';
9
+ import { readSettings, writeSettings, type TSettingsData } from '@robota-sdk/agent-framework';
10
+
11
+ import { WsTransport } from './ws/index.js';
12
+
13
+ import type { TUniversalValue } from '@robota-sdk/agent-core';
14
+ import type { IInteractiveSession } from '@robota-sdk/agent-framework';
15
+ import type {
16
+ IConfigurableTransport,
17
+ ITransportConfig,
18
+ ITransportEntry,
19
+ } from '@robota-sdk/agent-interface-transport';
20
+
21
+ export class TransportRegistry {
22
+ private readonly entries = new Map<string, IConfigurableTransport<IInteractiveSession>>();
23
+ private readonly settingsPath: string;
24
+
25
+ constructor(settingsPath: string) {
26
+ this.settingsPath = settingsPath;
27
+ }
28
+
29
+ register(transport: IConfigurableTransport<IInteractiveSession>): void {
30
+ this.entries.set(transport.name, transport);
31
+ }
32
+
33
+ getAll(): ITransportEntry<IInteractiveSession>[] {
34
+ const saved = this.readTransportSettings();
35
+ return Array.from(this.entries.values()).map((transport) => ({
36
+ transport,
37
+ config: this.resolveConfig(transport, saved[transport.name]),
38
+ }));
39
+ }
40
+
41
+ getEnabled(): IConfigurableTransport<IInteractiveSession>[] {
42
+ return this.getAll()
43
+ .filter((e) => e.config.enabled)
44
+ .map((e) => e.transport);
45
+ }
46
+
47
+ async setEnabled(name: string, enabled: boolean): Promise<void> {
48
+ const settings = readSettings(this.settingsPath);
49
+ const transports = (settings.transports ?? {}) as TSettingsData;
50
+ const entry = (transports[name] ?? {}) as TSettingsData;
51
+ transports[name] = { ...entry, enabled } as TSettingsData;
52
+ settings.transports = transports;
53
+ writeSettings(this.settingsPath, settings);
54
+ }
55
+
56
+ async setOptions(name: string, options: Record<string, TUniversalValue>): Promise<void> {
57
+ const settings = readSettings(this.settingsPath);
58
+ const transports = (settings.transports ?? {}) as TSettingsData;
59
+ const entry = (transports[name] ?? {}) as TSettingsData;
60
+ transports[name] = { ...entry, options: options as TSettingsData } as TSettingsData;
61
+ settings.transports = transports;
62
+ writeSettings(this.settingsPath, settings);
63
+ }
64
+
65
+ async startAll(session: IInteractiveSession): Promise<void> {
66
+ const enabled = this.getEnabled();
67
+ for (const transport of enabled) {
68
+ transport.attach(session);
69
+ await transport.start();
70
+ }
71
+ }
72
+
73
+ async stopAll(): Promise<void> {
74
+ for (const transport of this.entries.values()) {
75
+ await transport.stop();
76
+ }
77
+ }
78
+
79
+ private resolveConfig(
80
+ transport: IConfigurableTransport<IInteractiveSession>,
81
+ saved?: TSettingsData,
82
+ ): ITransportConfig {
83
+ const enabled = (saved?.enabled as boolean | undefined) ?? transport.defaultEnabled;
84
+ const options = (saved?.options as Record<string, TUniversalValue> | undefined) ?? {};
85
+ return { enabled, options };
86
+ }
87
+
88
+ private readTransportSettings(): Record<string, TSettingsData> {
89
+ const settings = readSettings(this.settingsPath);
90
+ const raw = settings.transports;
91
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};
92
+ return raw as Record<string, TSettingsData>;
93
+ }
94
+ }
95
+
96
+ export function createDefaultTransportRegistry(): TransportRegistry {
97
+ const registry = new TransportRegistry(getUserSettingsPath());
98
+ registry.register(new WsTransport());
99
+ return registry;
100
+ }
package/src/tui/App.tsx CHANGED
@@ -1,47 +1,48 @@
1
- import React, { useState, useEffect, useMemo, useCallback } from 'react';
2
- import { Box, Text, useApp, useInput } from 'ink';
3
- import type { IAIProvider } from '@robota-sdk/agent-core';
4
- import type { TPermissionMode } from '@robota-sdk/agent-core';
5
- import type {
6
- IBackgroundTaskRunner,
7
- ICommandHostAdapters,
8
- ICommandModule,
9
- IInteractiveSession,
10
- IInteractiveSessionStore,
11
- TSubagentRunnerFactory,
12
- TShellExecFn,
13
- IExecutionDetailPage,
14
- } from '@robota-sdk/agent-framework';
15
- import { listResumableSessionSummaries } from '@robota-sdk/agent-framework';
16
1
  import { createSystemMessage, messageToHistoryEntry } from '@robota-sdk/agent-core';
17
- import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
2
+ import { listResumableSessionSummaries } from '@robota-sdk/agent-framework';
3
+ import { Box, Text, useApp, useInput } from 'ink';
4
+ import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
5
+
6
+ import BackgroundTaskPanel from './BackgroundTaskPanel.js';
7
+ import ConfirmPrompt from './ConfirmPrompt.js';
8
+ import {
9
+ countActiveBackgroundWorkspaceEntries,
10
+ getDefaultBackgroundWorkspaceEntries,
11
+ } from './execution-workspace-view-model.js';
12
+ import ExecutionWorkspaceDetailPane from './ExecutionWorkspaceDetailPane.js';
13
+ import ExecutionWorkspaceSwitcher from './ExecutionWorkspaceSwitcher.js';
14
+ import { formatModelChangeConfirmationMessage } from './hooks/model-change-side-effect.js';
18
15
  import { useInteractiveSession } from './hooks/useInteractiveSession.js';
19
16
  import { usePluginCallbacks } from './hooks/usePluginCallbacks.js';
20
17
  import { useSideEffects } from './hooks/useSideEffects.js';
21
18
  import { useStatusLineSettings } from './hooks/useStatusLineSettings.js';
22
- import MessageList from './MessageList.js';
23
- import SessionStatusBar from './SessionStatusBar.js';
24
19
  import InputArea from './InputArea.js';
25
- import ConfirmPrompt from './ConfirmPrompt.js';
26
20
  import InteractivePrompt from './InteractivePrompt.js';
21
+ import MessageList from './MessageList.js';
27
22
  import PermissionPrompt from './PermissionPrompt.js';
28
- import StreamingIndicator from './StreamingIndicator.js';
29
23
  import PluginTUI from './PluginTUI.js';
30
- import TransportTUI from './TransportTUI.js';
31
24
  import SessionPicker from './SessionPicker.js';
32
- import BackgroundTaskPanel from './BackgroundTaskPanel.js';
33
- import ExecutionWorkspaceSwitcher from './ExecutionWorkspaceSwitcher.js';
34
- import ExecutionWorkspaceDetailPane from './ExecutionWorkspaceDetailPane.js';
35
- import UpdateNotice from './UpdateNotice.js';
36
- import { formatModelChangeConfirmationMessage } from './hooks/model-change-side-effect.js';
37
- import {
38
- countActiveBackgroundWorkspaceEntries,
39
- getDefaultBackgroundWorkspaceEntries,
40
- } from './execution-workspace-view-model.js';
25
+ import SessionStatusBar from './SessionStatusBar.js';
26
+ import StreamingIndicator from './StreamingIndicator.js';
27
+ import TransportTUI from './TransportTUI.js';
41
28
  import { TuiCliAdapterProvider } from './tui-cli-adapter-context.js';
29
+ import UpdateNotice from './UpdateNotice.js';
30
+
42
31
  import type { ITuiCliAdapter } from './tui-cli-adapter.js';
32
+ import type { TPermissionMode } from '@robota-sdk/agent-core';
33
+ import type { IAIProvider } from '@robota-sdk/agent-core';
34
+ import type {
35
+ IBackgroundTaskRunner,
36
+ ICommandHostAdapters,
37
+ ICommandModule,
38
+ IInteractiveSession,
39
+ IInteractiveSessionStore,
40
+ TSubagentRunnerFactory,
41
+ TShellExecFn,
42
+ IExecutionDetailPage,
43
+ } from '@robota-sdk/agent-framework';
43
44
  import type { CommandRegistry } from '@robota-sdk/agent-framework';
44
- import type { ITuiCommandInteraction } from './command-interaction.js';
45
+ import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
45
46
 
46
47
  interface IProps {
47
48
  cwd: string;
@@ -68,7 +69,6 @@ interface IProps {
68
69
  cliAdapter: ITuiCliAdapter;
69
70
  reloadPluginCommandSource?: (registry: CommandRegistry) => void;
70
71
  agentName?: string;
71
- resolveInteraction?: (commandName: string) => ITuiCommandInteraction | undefined;
72
72
  }
73
73
 
74
74
  export default function App(props: IProps): React.ReactElement {
@@ -150,6 +150,7 @@ function AppInner(
150
150
  const [executionDetailError, setExecutionDetailError] = useState<string | undefined>();
151
151
  const [isExecutionDetailLoading, setIsExecutionDetailLoading] = useState(false);
152
152
  const [statusLineSettings, setStatusLineSettings] = useStatusLineSettings();
153
+ const [gitRefreshToken, setGitRefreshToken] = useState(0);
153
154
  const backgroundWorkspaceEntries = useMemo(
154
155
  () => getDefaultBackgroundWorkspaceEntries(executionWorkspaceSnapshot),
155
156
  [executionWorkspaceSnapshot],
@@ -219,6 +220,23 @@ function AppInner(
219
220
  [selectedExecutionEntry, handleSubmit, interactiveSession],
220
221
  );
221
222
 
223
+ const handleSubmitWithGitRefresh = useCallback(
224
+ async (input: string): Promise<void> => {
225
+ setGitRefreshToken((t) => t + 1);
226
+ await handleSubmitWithRouting(input);
227
+ },
228
+ [handleSubmitWithRouting],
229
+ );
230
+
231
+ // Refresh git branch when AI response completes.
232
+ const wasThinkingRef = useRef(false);
233
+ useEffect(() => {
234
+ if (wasThinkingRef.current && !isThinking) {
235
+ setGitRefreshToken((t) => t + 1);
236
+ }
237
+ wasThinkingRef.current = isThinking;
238
+ }, [isThinking]);
239
+
222
240
  // Sync session name from InteractiveSession when resuming
223
241
  useEffect(() => {
224
242
  const name = interactiveSession?.getName?.();
@@ -452,9 +470,10 @@ function AppInner(
452
470
  sessionName={sessionName}
453
471
  settings={statusLineSettings}
454
472
  activeAgentLabel={activeAgentLabel}
473
+ gitRefreshToken={gitRefreshToken}
455
474
  />
456
475
  <InputArea
457
- onSubmit={handleSubmitWithRouting}
476
+ onSubmit={handleSubmitWithGitRefresh}
458
477
  onCancelQueue={handleCancelQueue}
459
478
  isDisabled={
460
479
  !!permissionRequest ||
@@ -472,7 +491,6 @@ function AppInner(
472
491
  registry={registry}
473
492
  sessionName={sessionName}
474
493
  history={history}
475
- resolveInteraction={props.resolveInteraction}
476
494
  />
477
495
  {/* Permanent blank line below input — required for Korean IME stability. */}
478
496
  <Text> </Text>