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

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 (37) hide show
  1. package/dist/node/headless/index.cjs +1 -1
  2. package/dist/node/{headless-CsZFelG9.cjs → headless-DJ5pnxM6.cjs} +1 -1
  3. package/dist/node/http/index.cjs +1 -1
  4. package/dist/node/{http-CM3TJhrF.cjs → http-CuQE6V6t.cjs} +1 -1
  5. package/dist/node/{index-CAr3ioVh.d.ts → index-CSgNoyPK.d.ts} +23 -2
  6. package/dist/node/index-CSgNoyPK.d.ts.map +1 -0
  7. package/dist/node/{index--Ti9NzQX.d.ts → index-_dNm-2J3.d.ts} +23 -2
  8. package/dist/node/index-_dNm-2J3.d.ts.map +1 -0
  9. package/dist/node/index.cjs +1 -1
  10. package/dist/node/index.d.ts +2 -2
  11. package/dist/node/index.js +1 -1
  12. package/dist/node/mcp/index.cjs +1 -1
  13. package/dist/node/{mcp-DcHuGokt.cjs → mcp-BiJsIywJ.cjs} +1 -1
  14. package/dist/node/tui/index.cjs +1 -1
  15. package/dist/node/tui/index.d.ts +2 -2
  16. package/dist/node/tui/index.js +1 -1
  17. package/dist/node/tui-BIpIcT7-.cjs +24 -0
  18. package/dist/node/tui-DBLn1T15.js +25 -0
  19. package/dist/node/tui-DBLn1T15.js.map +1 -0
  20. package/dist/node/ws/index.cjs +1 -1
  21. package/dist/node/{ws-COnIgnmn.cjs → ws-XRTSFZOK.cjs} +1 -1
  22. package/package.json +5 -5
  23. package/src/tui/App.tsx +3 -0
  24. package/src/tui/InputArea.tsx +64 -5
  25. package/src/tui/SlashAutocomplete.tsx +2 -3
  26. package/src/tui/__tests__/input-area-flow.test.ts +19 -0
  27. package/src/tui/command-interaction.ts +35 -0
  28. package/src/tui/flows/input-area-flow.ts +8 -1
  29. package/src/tui/index.ts +8 -0
  30. package/src/tui/interactions/CommandConfirm.tsx +35 -0
  31. package/src/tui/interactions/CommandPicker.tsx +76 -0
  32. package/src/tui/render.tsx +2 -0
  33. package/dist/node/index--Ti9NzQX.d.ts.map +0 -1
  34. package/dist/node/index-CAr3ioVh.d.ts.map +0 -1
  35. package/dist/node/tui-CeD_6rSo.cjs +0 -24
  36. package/dist/node/tui-zmDTPk4b.js +0 -25
  37. package/dist/node/tui-zmDTPk4b.js.map +0 -1
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../ws-COnIgnmn.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-XRTSFZOK.cjs`);exports.WsTransport=e.t,exports.createWsHandler=e.r,exports.createWsTransport=e.n;
@@ -1 +1 @@
1
- require(`./tui-CeD_6rSo.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}});
1
+ require(`./tui-BIpIcT7-.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-transport",
3
- "version": "3.0.0-beta.64",
3
+ "version": "3.0.0-beta.65",
4
4
  "description": "Consolidated transport package for Robota SDK — TUI, headless, HTTP, WebSocket, and MCP adapters",
5
5
  "type": "module",
6
6
  "main": "dist/node/index.js",
@@ -108,9 +108,9 @@
108
108
  "string-width": "^8.2.0",
109
109
  "ws": "^8.18.3",
110
110
  "zod": "^3.24.4",
111
- "@robota-sdk/agent-interface-transport": "3.0.0-beta.64",
112
- "@robota-sdk/agent-core": "3.0.0-beta.64",
113
- "@robota-sdk/agent-framework": "3.0.0-beta.64"
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"
114
114
  },
115
115
  "devDependencies": {
116
116
  "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
@@ -123,7 +123,7 @@
123
123
  "tsx": "^4.7.0",
124
124
  "typescript": "^5.3.3",
125
125
  "vitest": "^1.6.1",
126
- "@robota-sdk/agent-command": "3.0.0-beta.64"
126
+ "@robota-sdk/agent-command": "3.0.0-beta.65"
127
127
  },
128
128
  "license": "MIT",
129
129
  "publishConfig": {
package/src/tui/App.tsx CHANGED
@@ -41,6 +41,7 @@ import {
41
41
  import { TuiCliAdapterProvider } from './tui-cli-adapter-context.js';
42
42
  import type { ITuiCliAdapter } from './tui-cli-adapter.js';
43
43
  import type { CommandRegistry } from '@robota-sdk/agent-framework';
44
+ import type { ITuiCommandInteraction } from './command-interaction.js';
44
45
 
45
46
  interface IProps {
46
47
  cwd: string;
@@ -67,6 +68,7 @@ interface IProps {
67
68
  cliAdapter: ITuiCliAdapter;
68
69
  reloadPluginCommandSource?: (registry: CommandRegistry) => void;
69
70
  agentName?: string;
71
+ resolveInteraction?: (commandName: string) => ITuiCommandInteraction | undefined;
70
72
  }
71
73
 
72
74
  export default function App(props: IProps): React.ReactElement {
@@ -470,6 +472,7 @@ function AppInner(
470
472
  registry={registry}
471
473
  sessionName={sessionName}
472
474
  history={history}
475
+ resolveInteraction={props.resolveInteraction}
473
476
  />
474
477
  {/* Permanent blank line below input — required for Korean IME stability. */}
475
478
  <Text> </Text>
@@ -8,6 +8,8 @@ import type { CommandRegistry, ICommand } from '@robota-sdk/agent-framework';
8
8
  import CjkTextInput from './CjkTextInput.js';
9
9
  import WaveText from './WaveText.js';
10
10
  import SlashAutocomplete from './SlashAutocomplete.js';
11
+ import CommandPicker from './interactions/CommandPicker.js';
12
+ import CommandConfirm from './interactions/CommandConfirm.js';
11
13
  import { expandPasteLabels } from './utils/paste-labels.js';
12
14
  import { useAutocomplete } from './hooks/useAutocomplete.js';
13
15
  import {
@@ -24,6 +26,17 @@ import {
24
26
  resolveTabCompletion,
25
27
  shouldSubmitInput,
26
28
  } from './flows/input-area-flow.js';
29
+ import {
30
+ isPickerInteraction,
31
+ isConfirmInteraction,
32
+ type ITuiCommandInteraction,
33
+ type ITuiPickerItem,
34
+ } from './command-interaction.js';
35
+
36
+ interface IActiveInteraction {
37
+ commandName: string;
38
+ interaction: ITuiCommandInteraction;
39
+ }
27
40
 
28
41
  interface IProps {
29
42
  onSubmit: (value: string) => void;
@@ -34,6 +47,7 @@ interface IProps {
34
47
  registry?: CommandRegistry;
35
48
  sessionName?: string;
36
49
  history?: readonly IHistoryEntry[];
50
+ resolveInteraction?: (commandName: string) => ITuiCommandInteraction | undefined;
37
51
  }
38
52
 
39
53
  /**
@@ -65,9 +79,11 @@ export default function InputArea({
65
79
  registry,
66
80
  sessionName,
67
81
  history,
82
+ resolveInteraction,
68
83
  }: IProps): React.ReactElement {
69
84
  const [value, setValue] = useState('');
70
85
  const [cursorHint, setCursorHint] = useState<number | null>(null);
86
+ const [activeInteraction, setActiveInteraction] = useState<IActiveInteraction | null>(null);
71
87
  const [historyState, setHistoryState] = useState(createPromptHistoryNavigationState);
72
88
  const [localPromptHistory, setLocalPromptHistory] = useState<string[]>([]);
73
89
  const restoredPromptHistory = useMemo(() => extractPromptHistory(history ?? []), [history]);
@@ -139,7 +155,8 @@ export default function InputArea({
139
155
  /** Enter: insert and execute command immediately */
140
156
  const enterSelectCommand = useCallback(
141
157
  (cmd: ICommand): void => {
142
- const result = resolveEnterCommandSelection(value, cmd);
158
+ const interaction = resolveInteraction?.(cmd.name);
159
+ const result = resolveEnterCommandSelection(value, cmd, interaction);
143
160
  if (result.type === 'insert') {
144
161
  setValue(result.value);
145
162
  if (result.selectedIndex !== undefined) {
@@ -147,10 +164,17 @@ export default function InputArea({
147
164
  }
148
165
  return;
149
166
  }
150
- setValue('');
151
- submitPrompt(result.value);
167
+ if (result.type === 'open-interaction' && interaction?.onMissingArgs) {
168
+ setShowPopup(false);
169
+ setActiveInteraction({ commandName: result.commandName, interaction });
170
+ return;
171
+ }
172
+ if (result.type === 'submit') {
173
+ setValue('');
174
+ submitPrompt(result.value);
175
+ }
152
176
  },
153
- [value, submitPrompt, setSelectedIndex],
177
+ [value, submitPrompt, setSelectedIndex, resolveInteraction, setShowPopup],
154
178
  );
155
179
 
156
180
  const handleSubmit = useCallback(
@@ -238,9 +262,44 @@ export default function InputArea({
238
262
  return { left: '┌' + '─'.repeat(innerWidth), label: '', right: '┐' };
239
263
  })();
240
264
 
265
+ const handlePickerSelect = useCallback(
266
+ (item: ITuiPickerItem): void => {
267
+ if (!activeInteraction) return;
268
+ setActiveInteraction(null);
269
+ submitPrompt(`/${activeInteraction.commandName} ${item.value}`);
270
+ },
271
+ [activeInteraction, submitPrompt],
272
+ );
273
+
274
+ const handleConfirm = useCallback((): void => {
275
+ if (!activeInteraction) return;
276
+ setActiveInteraction(null);
277
+ submitPrompt(`/${activeInteraction.commandName}`);
278
+ }, [activeInteraction, submitPrompt]);
279
+
280
+ const handleInteractionCancel = useCallback((): void => {
281
+ setActiveInteraction(null);
282
+ }, []);
283
+
241
284
  return (
242
285
  <Box flexDirection="column">
243
- {showPopup && (
286
+ {activeInteraction && isPickerInteraction(activeInteraction.interaction) && (
287
+ <CommandPicker
288
+ commandName={activeInteraction.commandName}
289
+ interaction={activeInteraction.interaction}
290
+ onSelect={handlePickerSelect}
291
+ onCancel={handleInteractionCancel}
292
+ />
293
+ )}
294
+ {activeInteraction && isConfirmInteraction(activeInteraction.interaction) && (
295
+ <CommandConfirm
296
+ commandName={activeInteraction.commandName}
297
+ interaction={activeInteraction.interaction}
298
+ onConfirm={handleConfirm}
299
+ onCancel={handleInteractionCancel}
300
+ />
301
+ )}
302
+ {!activeInteraction && showPopup && (
244
303
  <SlashAutocomplete
245
304
  commands={filteredCommands}
246
305
  selectedIndex={selectedIndex}
@@ -51,8 +51,7 @@ function CommandRow(props: {
51
51
  const indicator = isSelected ? '▸ ' : ' ';
52
52
  const nameColor = isSelected ? 'cyan' : undefined;
53
53
  const dimmed = !isSelected;
54
- const displayLabel = cmd.displayName ?? cmd.name;
55
- const namePart = capName(displayLabel, nameColWidth);
54
+ const namePart = capName(cmd.name, nameColWidth);
56
55
  const text = showSlash
57
56
  ? `${indicator}/${namePart} ${cmd.description ?? ''}`
58
57
  : `${indicator}${namePart} ${cmd.description ?? ''}`;
@@ -82,7 +81,7 @@ export default function SlashAutocomplete({
82
81
 
83
82
  const nameColWidth = Math.min(
84
83
  NAME_COL_MAX,
85
- Math.max(...visibleCommands.map((c) => (c.displayName ?? c.name).length)),
84
+ Math.max(...visibleCommands.map((c) => c.name.length)),
86
85
  );
87
86
 
88
87
  return (
@@ -14,6 +14,7 @@ import {
14
14
  shouldSubmitInput,
15
15
  } from '../flows/input-area-flow.js';
16
16
  import type { ICommand } from '@robota-sdk/agent-framework';
17
+ import type { ITuiPickerInteraction } from '../command-interaction.js';
17
18
  import {
18
19
  createAssistantMessage,
19
20
  createSystemMessage,
@@ -66,6 +67,24 @@ describe('input area flow', () => {
66
67
  expect(result).toEqual({ type: 'submit', value: '/help' });
67
68
  });
68
69
 
70
+ it('Given interaction declared and no args When enter selects command Then open-interaction is returned', () => {
71
+ const result = resolveEnterCommandSelection('/ex', command('exit'), {
72
+ onMissingArgs: 'confirm',
73
+ });
74
+
75
+ expect(result).toEqual({ type: 'open-interaction', commandName: 'exit' });
76
+ });
77
+
78
+ it('Given interaction declared but subcommand selected (args present) When enter selects Then submits', () => {
79
+ const pickerInteraction: ITuiPickerInteraction = {
80
+ onMissingArgs: 'picker',
81
+ getItems: () => [],
82
+ };
83
+ const result = resolveEnterCommandSelection('/mode plan', command('plan'), pickerInteraction);
84
+
85
+ expect(result).toEqual({ type: 'submit', value: '/mode plan' });
86
+ });
87
+
69
88
  it('Given multiline paste When label change is created Then label is inserted at cursor', () => {
70
89
  const result = createPasteLabelChange('abef', 2, 7, 'c\nd\ne');
71
90
 
@@ -0,0 +1,35 @@
1
+ export type TOnMissingArgsAction = 'picker' | 'wizard' | 'confirm';
2
+
3
+ export interface ITuiPickerItem {
4
+ label: string;
5
+ value: string;
6
+ description?: string;
7
+ }
8
+
9
+ export interface ITuiCommandInteraction {
10
+ onMissingArgs?: TOnMissingArgsAction;
11
+ }
12
+
13
+ export interface ITuiPickerInteraction extends ITuiCommandInteraction {
14
+ onMissingArgs: 'picker';
15
+ getItems(): ITuiPickerItem[];
16
+ }
17
+
18
+ export interface ITuiConfirmInteraction extends ITuiCommandInteraction {
19
+ onMissingArgs: 'confirm';
20
+ message: string;
21
+ }
22
+
23
+ export type TAnyTuiCommandInteraction = ITuiPickerInteraction | ITuiConfirmInteraction;
24
+
25
+ export function isPickerInteraction(
26
+ interaction: ITuiCommandInteraction,
27
+ ): interaction is ITuiPickerInteraction {
28
+ return interaction.onMissingArgs === 'picker';
29
+ }
30
+
31
+ export function isConfirmInteraction(
32
+ interaction: ITuiCommandInteraction,
33
+ ): interaction is ITuiConfirmInteraction {
34
+ return interaction.onMissingArgs === 'confirm';
35
+ }
@@ -1,6 +1,7 @@
1
1
  import type { IHistoryEntry, TUniversalValue } from '@robota-sdk/agent-core';
2
2
  import type { ICommand } from '@robota-sdk/agent-framework';
3
3
  import { parseSlashInput } from '../hooks/useAutocomplete.js';
4
+ import type { ITuiCommandInteraction } from '../command-interaction.js';
4
5
 
5
6
  export interface IAutocompleteInputKey {
6
7
  upArrow?: boolean;
@@ -17,7 +18,8 @@ export type TPromptHistoryInputAction = 'previous' | 'next';
17
18
 
18
19
  export type TCommandSelectionResult =
19
20
  | { type: 'insert'; value: string; selectedIndex?: number }
20
- | { type: 'submit'; value: string };
21
+ | { type: 'submit'; value: string }
22
+ | { type: 'open-interaction'; commandName: string };
21
23
 
22
24
  export interface IPasteLabelChange {
23
25
  value: string;
@@ -154,11 +156,16 @@ export function resolveTabCompletion(value: string, command: ICommand): TCommand
154
156
  export function resolveEnterCommandSelection(
155
157
  value: string,
156
158
  command: ICommand,
159
+ interaction?: ITuiCommandInteraction,
157
160
  ): TCommandSelectionResult {
158
161
  const parsed = parseSlashInput(value);
159
162
  if (parsed.parentCommand) {
160
163
  return { type: 'submit', value: `/${parsed.parentCommand} ${command.name}` };
161
164
  }
165
+ // parentCommand is empty → no args provided beyond the command name itself
166
+ if (interaction?.onMissingArgs) {
167
+ return { type: 'open-interaction', commandName: command.name };
168
+ }
162
169
  if (command.subcommands && command.subcommands.length > 0) {
163
170
  return { type: 'insert', value: `/${command.name} `, selectedIndex: 0 };
164
171
  }
package/src/tui/index.ts CHANGED
@@ -1,3 +1,11 @@
1
1
  export { TuiTransport } from './tui-transport.js';
2
2
  export type { ITuiCliAdapter } from './tui-cli-adapter.js';
3
3
  export type { IRenderOptions } from './render.js';
4
+ export type {
5
+ TOnMissingArgsAction,
6
+ ITuiPickerItem,
7
+ ITuiCommandInteraction,
8
+ ITuiPickerInteraction,
9
+ ITuiConfirmInteraction,
10
+ TAnyTuiCommandInteraction,
11
+ } from './command-interaction.js';
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import type { ITuiConfirmInteraction } from '../command-interaction.js';
4
+
5
+ interface IProps {
6
+ commandName: string;
7
+ interaction: ITuiConfirmInteraction;
8
+ onConfirm: () => void;
9
+ onCancel: () => void;
10
+ }
11
+
12
+ export default function CommandConfirm({
13
+ commandName,
14
+ interaction,
15
+ onConfirm,
16
+ onCancel,
17
+ }: IProps): React.ReactElement {
18
+ useInput((input, key) => {
19
+ if (key.return || input === 'y' || input === 'Y') {
20
+ onConfirm();
21
+ } else if (key.escape || input === 'n' || input === 'N') {
22
+ onCancel();
23
+ }
24
+ });
25
+
26
+ return (
27
+ <Box paddingX={1}>
28
+ <Text bold color="yellow">
29
+ /{commandName}:{' '}
30
+ </Text>
31
+ <Text>{interaction.message} </Text>
32
+ <Text dimColor>[y/n]</Text>
33
+ </Box>
34
+ );
35
+ }
@@ -0,0 +1,76 @@
1
+ import React, { useState } from 'react';
2
+ import { Box, Text, useInput, useStdout } from 'ink';
3
+ import type { ITuiPickerInteraction, ITuiPickerItem } from '../command-interaction.js';
4
+
5
+ interface IProps {
6
+ commandName: string;
7
+ interaction: ITuiPickerInteraction;
8
+ onSelect: (item: ITuiPickerItem) => void;
9
+ onCancel: () => void;
10
+ }
11
+
12
+ const MAX_VISIBLE = 8;
13
+ const OUTER_CHROME = 4;
14
+ const MIN_ROW_WIDTH = 40;
15
+
16
+ function useRowWidth(): number {
17
+ const { stdout } = useStdout();
18
+ return Math.max(MIN_ROW_WIDTH, (stdout.columns ?? 80) - OUTER_CHROME);
19
+ }
20
+
21
+ export default function CommandPicker({
22
+ commandName,
23
+ interaction,
24
+ onSelect,
25
+ onCancel,
26
+ }: IProps): React.ReactElement {
27
+ const items = interaction.getItems();
28
+ const [selectedIndex, setSelectedIndex] = useState(0);
29
+ const rowWidth = useRowWidth();
30
+
31
+ useInput((input, key) => {
32
+ if (key.upArrow) {
33
+ setSelectedIndex((i) => (i > 0 ? i - 1 : items.length - 1));
34
+ } else if (key.downArrow) {
35
+ setSelectedIndex((i) => (i < items.length - 1 ? i + 1 : 0));
36
+ } else if (key.return) {
37
+ const item = items[selectedIndex];
38
+ if (item) onSelect(item);
39
+ } else if (key.escape || input === 'q') {
40
+ onCancel();
41
+ }
42
+ });
43
+
44
+ const scrollOffset = (() => {
45
+ if (items.length <= MAX_VISIBLE) return 0;
46
+ if (selectedIndex < MAX_VISIBLE) return 0;
47
+ return Math.min(selectedIndex - MAX_VISIBLE + 1, items.length - MAX_VISIBLE);
48
+ })();
49
+ const visibleItems = items.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
50
+
51
+ return (
52
+ <Box flexDirection="column" borderStyle="round" borderColor="cyan" paddingX={1}>
53
+ <Text bold color="cyan">
54
+ /{commandName}
55
+ </Text>
56
+ {visibleItems.map((item, i) => {
57
+ const isSelected = scrollOffset + i === selectedIndex;
58
+ const indicator = isSelected ? '▸ ' : ' ';
59
+ return (
60
+ <Box key={item.value} width={rowWidth}>
61
+ <Text
62
+ color={isSelected ? 'cyan' : undefined}
63
+ dimColor={!isSelected}
64
+ wrap="truncate-end"
65
+ >
66
+ {indicator}
67
+ {item.label}
68
+ {item.description != null ? ` ${item.description}` : ''}
69
+ </Text>
70
+ </Box>
71
+ );
72
+ })}
73
+ <Text dimColor>↑↓ navigate · Enter select · Esc cancel</Text>
74
+ </Box>
75
+ );
76
+ }
@@ -19,6 +19,7 @@ import type {
19
19
  } from '@robota-sdk/agent-framework';
20
20
  import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
21
21
  import type { ITuiCliAdapter } from './tui-cli-adapter.js';
22
+ import type { ITuiCommandInteraction } from './command-interaction.js';
22
23
 
23
24
  export interface IRenderOptions {
24
25
  cwd: string;
@@ -45,6 +46,7 @@ export interface IRenderOptions {
45
46
  cliAdapter: ITuiCliAdapter;
46
47
  reloadPluginCommandSource?: (registry: CommandRegistry) => void;
47
48
  agentName?: string;
49
+ resolveInteraction?: (commandName: string) => ITuiCommandInteraction | undefined;
48
50
  }
49
51
 
50
52
  export async function renderApp(options: IRenderOptions): Promise<void> {
@@ -1 +0,0 @@
1
- {"version":3,"file":"index--Ti9NzQX.d.ts","names":[],"sources":["../../src/tui/tui-cli-adapter.ts","../../src/tui/render.tsx","../../src/tui/tui-transport.ts"],"mappings":";;;;;UAOiB,cAAA;EACf,mBAAA;EACA,YAAA,CAAa,IAAA,WAAe,MAAA,SAAe,eAAA;EAC3C,aAAA,CAAc,IAAA,UAAc,QAAA,EAAU,MAAA,SAAe,eAAA;EACrD,cAAA,CAAe,IAAA;EACf,uBAAA,CACE,IAAA,UACA,KAAA,EAAO,+BAAA,GACN,0BAAA;EACH,yBAAA,CAA0B,QAAA,EAAU,eAAA;EACpC,sBAAA,CACE,GAAA,UACA,OAAA,UACA,OAAA;IAAY,gBAAA;EAAA;IACT,OAAA;EAAA;EACL,YAAA,CAAa,GAAA;EACb,sBAAA,CAAuB,IAAA;AAAA;;;UCDR,cAAA;EACf,GAAA;EACA,QAAA,EAAU,WAAA;EACV,gBAAA;EACA,YAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA,GAAiB,eAAA;EACjB,QAAA;EACA,OAAA;EACA,YAAA,GAAe,wBAAA;EACf,eAAA;EACA,wBAAA;EACA,WAAA;EACA,WAAA;EACA,qBAAA,GAAwB,qBAAA;EACxB,qBAAA,GAAwB,sBAAA;EACxB,cAAA,YAA0B,cAAA;EAC1B,mBAAA,GAAsB,oBAAA;EACtB,SAAA,GAAY,YAAA;EACZ,mBAAA,GAAsB,OAAA;EACtB,iBAAA,GAAoB,sBAAA,CAAuB,mBAAA;EAC3C,UAAA,EAAY,cAAA;EACZ,yBAAA,IAA6B,QAAA,EAAU,eAAA;EACvC,SAAA;AAAA;;;cCzCW,YAAA,YAAwB,sBAAA,CAAuB,mBAAA;EAAA,SACjD,IAAA;EAAA,SACA,cAAA;EAAA,SACA,aAAA;EAAA,iBAEQ,OAAA;cAEL,OAAA,EAAS,cAAA;EAIrB,MAAA,CAAO,QAAA,EAAU,mBAAA;EAIX,KAAA,CAAA,GAAS,OAAA;EAIT,IAAA,CAAA,GAAQ,OAAA;EAId,eAAA,CAAgB,QAAA,EAAU,MAAA,SAAe,eAAA;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-CAr3ioVh.d.ts","names":[],"sources":["../../src/tui/tui-cli-adapter.ts","../../src/tui/render.tsx","../../src/tui/tui-transport.ts"],"mappings":";;;;;UAOiB,cAAA;EACf,mBAAA;EACA,YAAA,CAAa,IAAA,WAAe,MAAA,SAAe,eAAA;EAC3C,aAAA,CAAc,IAAA,UAAc,QAAA,EAAU,MAAA,SAAe,eAAA;EACrD,cAAA,CAAe,IAAA;EACf,uBAAA,CACE,IAAA,UACA,KAAA,EAAO,+BAAA,GACN,0BAAA;EACH,yBAAA,CAA0B,QAAA,EAAU,eAAA;EACpC,sBAAA,CACE,GAAA,UACA,OAAA,UACA,OAAA;IAAY,gBAAA;EAAA;IACT,OAAA;EAAA;EACL,YAAA,CAAa,GAAA;EACb,sBAAA,CAAuB,IAAA;AAAA;;;UCDR,cAAA;EACf,GAAA;EACA,QAAA,EAAU,WAAA;EACV,gBAAA;EACA,YAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA,GAAiB,eAAA;EACjB,QAAA;EACA,OAAA;EACA,YAAA,GAAe,wBAAA;EACf,eAAA;EACA,wBAAA;EACA,WAAA;EACA,WAAA;EACA,qBAAA,GAAwB,qBAAA;EACxB,qBAAA,GAAwB,sBAAA;EACxB,cAAA,YAA0B,cAAA;EAC1B,mBAAA,GAAsB,oBAAA;EACtB,SAAA,GAAY,YAAA;EACZ,mBAAA,GAAsB,OAAA;EACtB,iBAAA,GAAoB,sBAAA,CAAuB,mBAAA;EAC3C,UAAA,EAAY,cAAA;EACZ,yBAAA,IAA6B,QAAA,EAAU,eAAA;EACvC,SAAA;AAAA;;;cCzCW,YAAA,YAAwB,sBAAA,CAAuB,mBAAA;EAAA,SACjD,IAAA;EAAA,SACA,cAAA;EAAA,SACA,aAAA;EAAA,iBAEQ,OAAA;cAEL,OAAA,EAAS,cAAA;EAIrB,MAAA,CAAO,QAAA,EAAU,mBAAA;EAIX,KAAA,CAAA,GAAS,OAAA;EAIT,IAAA,CAAA,GAAQ,OAAA;EAId,eAAA,CAAgB,QAAA,EAAU,MAAA,SAAe,eAAA;AAAA"}