@robota-sdk/agent-transport 3.0.0-beta.75 → 3.0.0-beta.77

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 (231) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +17 -14
  3. package/dist/node/headless/index.cjs +1 -1
  4. package/dist/node/headless/index.d.ts +1 -1
  5. package/dist/node/headless/index.js +1 -1
  6. package/dist/node/{headless-mRYilLfC.js → headless-DG_MEbf0.js} +7 -4
  7. package/dist/node/headless-DG_MEbf0.js.map +1 -0
  8. package/dist/node/headless-xq77JBq0.cjs +18 -0
  9. package/dist/node/{index-E8Gx4-lc.d.ts → index-CrJ2Lhn3.d.ts} +16 -3
  10. package/dist/node/index-CrJ2Lhn3.d.ts.map +1 -0
  11. package/dist/node/index-JIvzrtaA.d.ts +52 -0
  12. package/dist/node/index-JIvzrtaA.d.ts.map +1 -0
  13. package/dist/node/{index-CYl7ksS6.d.ts → index-JTD_xtEQ.d.ts} +15 -2
  14. package/dist/node/index-JTD_xtEQ.d.ts.map +1 -0
  15. package/dist/node/index-y_HBOfzO.d.ts +52 -0
  16. package/dist/node/index-y_HBOfzO.d.ts.map +1 -0
  17. package/dist/node/index.cjs +1 -1
  18. package/dist/node/index.d.ts +10 -9
  19. package/dist/node/index.d.ts.map +1 -1
  20. package/dist/node/index.js +1 -1
  21. package/dist/node/index.js.map +1 -1
  22. package/dist/node/programmatic/index.cjs +1 -0
  23. package/dist/node/programmatic/index.d.ts +2 -0
  24. package/dist/node/programmatic/index.js +1 -0
  25. package/dist/node/programmatic-6wLf68qJ.cjs +1 -0
  26. package/dist/node/programmatic-70dxIXtS.js +2 -0
  27. package/dist/node/programmatic-70dxIXtS.js.map +1 -0
  28. package/dist/node/testing/index.cjs +1 -1
  29. package/dist/node/testing/index.d.ts +2 -21
  30. package/dist/node/testing/index.js +1 -2
  31. package/package.json +15 -71
  32. package/src/__tests__/programmatic/programmatic-ask-user.test.ts +32 -0
  33. package/src/__tests__/programmatic/programmatic-driver.test.ts +107 -0
  34. package/src/__tests__/programmatic/programmatic-flows.test.ts +140 -0
  35. package/src/__tests__/transport-registry.stop-all.test.ts +53 -0
  36. package/src/headless/HeadlessInteractionChannel.ts +22 -5
  37. package/src/headless/__tests__/headless-goal-runner.test.ts +108 -0
  38. package/src/headless/__tests__/headless-runner.test.ts +2 -1
  39. package/src/headless/headless-runner.ts +67 -1
  40. package/src/headless/headless-stream-json.ts +1 -1
  41. package/src/headless/print-terminal.ts +1 -0
  42. package/src/index.ts +2 -5
  43. package/src/programmatic/ProgrammaticInteractionChannel.ts +82 -0
  44. package/src/programmatic/createProgrammaticAgent.ts +71 -0
  45. package/src/programmatic/index.ts +5 -0
  46. package/src/testing/index.ts +7 -4
  47. package/src/transport-registry.ts +15 -12
  48. package/dist/node/headless-CT2ibQnr.cjs +0 -15
  49. package/dist/node/headless-mRYilLfC.js.map +0 -1
  50. package/dist/node/http/index.cjs +0 -1
  51. package/dist/node/http/index.d.ts +0 -2
  52. package/dist/node/http/index.js +0 -1
  53. package/dist/node/http-2Jiuflc1.js +0 -2
  54. package/dist/node/http-2Jiuflc1.js.map +0 -1
  55. package/dist/node/http-CBAvefLw.cjs +0 -1
  56. package/dist/node/index-BNccqSpv.d.ts +0 -86
  57. package/dist/node/index-BNccqSpv.d.ts.map +0 -1
  58. package/dist/node/index-BUhHIf7X.d.ts +0 -86
  59. package/dist/node/index-BUhHIf7X.d.ts.map +0 -1
  60. package/dist/node/index-BnAGE-u9.d.ts +0 -33
  61. package/dist/node/index-BnAGE-u9.d.ts.map +0 -1
  62. package/dist/node/index-BrQ4gGw0.d.ts +0 -213
  63. package/dist/node/index-BrQ4gGw0.d.ts.map +0 -1
  64. package/dist/node/index-CYl7ksS6.d.ts.map +0 -1
  65. package/dist/node/index-CoeBF21y.d.ts +0 -213
  66. package/dist/node/index-CoeBF21y.d.ts.map +0 -1
  67. package/dist/node/index-DHt-2VQ-.d.ts +0 -46
  68. package/dist/node/index-DHt-2VQ-.d.ts.map +0 -1
  69. package/dist/node/index-DMwKN5Le.d.ts +0 -33
  70. package/dist/node/index-DMwKN5Le.d.ts.map +0 -1
  71. package/dist/node/index-E8Gx4-lc.d.ts.map +0 -1
  72. package/dist/node/index-c0M42fsA.d.ts +0 -46
  73. package/dist/node/index-c0M42fsA.d.ts.map +0 -1
  74. package/dist/node/mcp/index.cjs +0 -1
  75. package/dist/node/mcp/index.d.ts +0 -2
  76. package/dist/node/mcp/index.js +0 -1
  77. package/dist/node/mcp-BOglBJNy.cjs +0 -1
  78. package/dist/node/mcp-D3BBVK7C.js +0 -2
  79. package/dist/node/mcp-D3BBVK7C.js.map +0 -1
  80. package/dist/node/rolldown-runtime-CMqjfN_6.cjs +0 -1
  81. package/dist/node/testing/index.d.ts.map +0 -1
  82. package/dist/node/testing/index.js.map +0 -1
  83. package/dist/node/tui/index.cjs +0 -1
  84. package/dist/node/tui/index.d.ts +0 -2
  85. package/dist/node/tui/index.js +0 -1
  86. package/dist/node/tui-CcH5EsQh.js +0 -25
  87. package/dist/node/tui-CcH5EsQh.js.map +0 -1
  88. package/dist/node/tui-DznRbcku.cjs +0 -24
  89. package/dist/node/ws/index.cjs +0 -1
  90. package/dist/node/ws/index.d.ts +0 -2
  91. package/dist/node/ws/index.js +0 -1
  92. package/dist/node/ws-Dc2RUwVs.js +0 -2
  93. package/dist/node/ws-Dc2RUwVs.js.map +0 -1
  94. package/dist/node/ws-QNMQn5kg.cjs +0 -1
  95. package/src/http/__tests__/http-transport.test.ts +0 -55
  96. package/src/http/__tests__/routes.test.ts +0 -168
  97. package/src/http/http-transport.ts +0 -41
  98. package/src/http/index.ts +0 -4
  99. package/src/http/routes.ts +0 -152
  100. package/src/mcp/__tests__/mcp-server.test.ts +0 -66
  101. package/src/mcp/__tests__/mcp-transport.test.ts +0 -46
  102. package/src/mcp/index.ts +0 -4
  103. package/src/mcp/mcp-server.ts +0 -163
  104. package/src/mcp/mcp-transport.ts +0 -48
  105. package/src/testing/__tests__/scripted-provider.test.ts +0 -73
  106. package/src/testing/scripted-provider.ts +0 -73
  107. package/src/tui/App.tsx +0 -491
  108. package/src/tui/BackgroundTaskPanel.tsx +0 -36
  109. package/src/tui/CjkTextInput.tsx +0 -199
  110. package/src/tui/ConfirmPrompt.tsx +0 -70
  111. package/src/tui/ContextWarningBanner.tsx +0 -34
  112. package/src/tui/ExecutionWorkspaceDetailPane.tsx +0 -64
  113. package/src/tui/ExecutionWorkspaceSwitcher.tsx +0 -187
  114. package/src/tui/InputArea.tsx +0 -310
  115. package/src/tui/InteractivePrompt.tsx +0 -59
  116. package/src/tui/ListPicker.tsx +0 -95
  117. package/src/tui/MenuSelect.tsx +0 -104
  118. package/src/tui/MessageList.tsx +0 -284
  119. package/src/tui/PermissionPrompt.tsx +0 -86
  120. package/src/tui/PluginTUI.tsx +0 -258
  121. package/src/tui/SessionPicker.tsx +0 -68
  122. package/src/tui/SessionStatusBar.tsx +0 -73
  123. package/src/tui/SlashAutocomplete.tsx +0 -110
  124. package/src/tui/StatusBar.tsx +0 -236
  125. package/src/tui/StreamingIndicator.tsx +0 -93
  126. package/src/tui/TextPrompt.tsx +0 -81
  127. package/src/tui/ToolCommandOutput.tsx +0 -39
  128. package/src/tui/ToolDiffBlock.tsx +0 -32
  129. package/src/tui/TransportTUI.tsx +0 -117
  130. package/src/tui/TuiInteractionChannel.ts +0 -495
  131. package/src/tui/UpdateNotice.tsx +0 -14
  132. package/src/tui/UsageSummaryEntry.tsx +0 -39
  133. package/src/tui/WaveText.tsx +0 -44
  134. package/src/tui/__tests__/InteractivePrompt.test.tsx +0 -82
  135. package/src/tui/__tests__/ListPicker.test.tsx +0 -159
  136. package/src/tui/__tests__/MenuSelect.test.tsx +0 -103
  137. package/src/tui/__tests__/PluginTUI.test.tsx +0 -167
  138. package/src/tui/__tests__/SlashAutocomplete.test.tsx +0 -140
  139. package/src/tui/__tests__/TextPrompt.test.tsx +0 -98
  140. package/src/tui/__tests__/TuiInteractionChannel.display-contract.test.ts +0 -239
  141. package/src/tui/__tests__/TuiInteractionChannel.lifecycle.test.ts +0 -297
  142. package/src/tui/__tests__/TuiInteractionChannel.requestAction.test.ts +0 -124
  143. package/src/tui/__tests__/UpdateNotice.test.tsx +0 -15
  144. package/src/tui/__tests__/abort-after-permission.test.tsx +0 -169
  145. package/src/tui/__tests__/abort-streaming-e2e.test.tsx +0 -183
  146. package/src/tui/__tests__/background-task-panel.test.tsx +0 -53
  147. package/src/tui/__tests__/background-task-row-format.test.ts +0 -59
  148. package/src/tui/__tests__/channel-factory-integration.test.ts +0 -138
  149. package/src/tui/__tests__/cjk-text-input-flow.test.ts +0 -109
  150. package/src/tui/__tests__/cjk-text-input.test.ts +0 -191
  151. package/src/tui/__tests__/command-effect-handler.test.ts +0 -127
  152. package/src/tui/__tests__/command-output-summary.test.ts +0 -95
  153. package/src/tui/__tests__/compact-event-bridge.test.ts +0 -20
  154. package/src/tui/__tests__/confirm-permission-flow.test.ts +0 -130
  155. package/src/tui/__tests__/confirm-prompt.test.tsx +0 -87
  156. package/src/tui/__tests__/execution-workspace-switcher.test.tsx +0 -110
  157. package/src/tui/__tests__/execution-workspace-view-model.test.ts +0 -93
  158. package/src/tui/__tests__/fixtures/provider-setup-prompt-driver.tsx +0 -125
  159. package/src/tui/__tests__/input-area-flow.test.ts +0 -164
  160. package/src/tui/__tests__/message-list-rendering.test.tsx +0 -353
  161. package/src/tui/__tests__/prompt-queue.test.tsx +0 -255
  162. package/src/tui/__tests__/provider-setup-pty-e2e.test.ts +0 -233
  163. package/src/tui/__tests__/pty/pty-driver.ts +0 -135
  164. package/src/tui/__tests__/pty/tui-pty.ptytest.ts +0 -61
  165. package/src/tui/__tests__/render-channel-options.test.ts +0 -32
  166. package/src/tui/__tests__/render-markdown.test.ts +0 -72
  167. package/src/tui/__tests__/selection-flow.test.ts +0 -61
  168. package/src/tui/__tests__/session-init-poller.test.ts +0 -102
  169. package/src/tui/__tests__/session-naming.test.ts +0 -64
  170. package/src/tui/__tests__/session-switch-channel.test.tsx +0 -307
  171. package/src/tui/__tests__/slash-routing-effects.test.ts +0 -228
  172. package/src/tui/__tests__/status-activity.test.ts +0 -71
  173. package/src/tui/__tests__/status-bar.test.tsx +0 -177
  174. package/src/tui/__tests__/streaming-indicator.test.tsx +0 -137
  175. package/src/tui/__tests__/text-prompt-flow.test.ts +0 -77
  176. package/src/tui/__tests__/tui-channel-init-failure.test.ts +0 -57
  177. package/src/tui/__tests__/tui-state-manager.test.ts +0 -401
  178. package/src/tui/background-task-row-format.ts +0 -53
  179. package/src/tui/command-interaction.ts +0 -9
  180. package/src/tui/command-output-summary.ts +0 -122
  181. package/src/tui/create-default-tui-cli-adapter.ts +0 -41
  182. package/src/tui/execution-workspace-view-model.ts +0 -123
  183. package/src/tui/flows/cjk-text-input-flow.ts +0 -285
  184. package/src/tui/flows/confirm-prompt-flow.ts +0 -45
  185. package/src/tui/flows/input-area-flow.ts +0 -189
  186. package/src/tui/flows/permission-prompt-flow.ts +0 -85
  187. package/src/tui/flows/selection-flow.ts +0 -126
  188. package/src/tui/flows/session-init-poller.ts +0 -77
  189. package/src/tui/flows/text-prompt-flow.ts +0 -98
  190. package/src/tui/hooks/command-effect-handler.ts +0 -97
  191. package/src/tui/hooks/command-effect-queue.ts +0 -39
  192. package/src/tui/hooks/side-effects-types.ts +0 -35
  193. package/src/tui/hooks/useAutocomplete.ts +0 -87
  194. package/src/tui/hooks/usePluginCallbacks.ts +0 -31
  195. package/src/tui/hooks/usePluginScreenData.ts +0 -85
  196. package/src/tui/hooks/useSideEffects.ts +0 -175
  197. package/src/tui/hooks/useSlashRouting.ts +0 -118
  198. package/src/tui/hooks/useStatusLineSettings.ts +0 -37
  199. package/src/tui/hooks/useTuiChannel.ts +0 -95
  200. package/src/tui/index.ts +0 -14
  201. package/src/tui/interactions/CommandConfirm.tsx +0 -36
  202. package/src/tui/interactions/CommandPicker.tsx +0 -77
  203. package/src/tui/interactions/__tests__/CommandConfirm.test.tsx +0 -124
  204. package/src/tui/interactions/__tests__/CommandPicker.test.tsx +0 -138
  205. package/src/tui/plugin-tui-handlers.ts +0 -163
  206. package/src/tui/render-markdown.ts +0 -130
  207. package/src/tui/render.tsx +0 -129
  208. package/src/tui/session-naming.ts +0 -33
  209. package/src/tui/status-activity.ts +0 -63
  210. package/src/tui/tui-cli-adapter-context.tsx +0 -13
  211. package/src/tui/tui-cli-adapter.ts +0 -25
  212. package/src/tui/tui-state-manager.ts +0 -226
  213. package/src/tui/tui-transport.ts +0 -35
  214. package/src/tui/types.ts +0 -15
  215. package/src/tui/utils/__tests__/edit-diff.test.ts +0 -426
  216. package/src/tui/utils/__tests__/paste-detection.test.ts +0 -116
  217. package/src/tui/utils/__tests__/paste-labels.test.ts +0 -46
  218. package/src/tui/utils/__tests__/tool-call-extractor.test.ts +0 -227
  219. package/src/tui/utils/__tests__/tool-diff-summary.test.ts +0 -104
  220. package/src/tui/utils/edit-diff.ts +0 -153
  221. package/src/tui/utils/paste-labels.ts +0 -9
  222. package/src/tui/utils/tool-call-extractor.ts +0 -92
  223. package/src/tui/utils/tool-diff-summary.ts +0 -75
  224. package/src/ws/__tests__/ws-handler.test.ts +0 -409
  225. package/src/ws/__tests__/ws-transport.test.ts +0 -53
  226. package/src/ws/index.ts +0 -13
  227. package/src/ws/ws-background-messages.ts +0 -170
  228. package/src/ws/ws-handler.ts +0 -280
  229. package/src/ws/ws-protocol.ts +0 -78
  230. package/src/ws/ws-transport-configurable.ts +0 -128
  231. package/src/ws/ws-transport.ts +0 -42
@@ -1,57 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import type { IAIProvider } from '@robota-sdk/agent-core';
3
-
4
- vi.mock('@robota-sdk/agent-framework', async (importOriginal) => {
5
- const mod = await importOriginal<typeof import('@robota-sdk/agent-framework')>();
6
-
7
- class FakeInteractiveSession {
8
- on(): void {}
9
- off(): void {}
10
- getFullHistory(): unknown[] {
11
- return [];
12
- }
13
- getContextState(): never {
14
- throw new Error('ENOENT: session store unreadable');
15
- }
16
- getName(): string | undefined {
17
- return undefined;
18
- }
19
- getSession(): { getSessionId: () => string } {
20
- return { getSessionId: () => 'test-session' };
21
- }
22
- async shutdown(): Promise<void> {}
23
- }
24
-
25
- return { ...mod, InteractiveSession: FakeInteractiveSession };
26
- });
27
-
28
- import { TuiInteractionChannel } from '../TuiInteractionChannel.js';
29
-
30
- describe('TuiInteractionChannel init failure surfacing', () => {
31
- beforeEach(() => {
32
- vi.useFakeTimers();
33
- });
34
-
35
- afterEach(() => {
36
- vi.useRealTimers();
37
- });
38
-
39
- it('TC-05: a real init error records a session-init-error entry and sets error state', async () => {
40
- const channel = new TuiInteractionChannel({
41
- cwd: '/tmp/project',
42
- provider: {} as IAIProvider,
43
- });
44
- await channel.start();
45
-
46
- vi.advanceTimersByTime(400);
47
-
48
- const entries = channel.stateManager.history;
49
- const initError = entries.find((e) => e.type === 'session-init-error');
50
- expect(initError).toBeDefined();
51
- const message = (initError?.data as { message?: string } | undefined)?.message ?? '';
52
- expect(message).toContain('Session initialization failed');
53
- expect(message).toContain('ENOENT');
54
-
55
- await channel.stop();
56
- });
57
- });
@@ -1,401 +0,0 @@
1
- /**
2
- * Unit tests for TuiStateManager — pure TypeScript, no React.
3
- * Tests event → state transitions that must survive any refactoring.
4
- */
5
-
6
- import { describe, it, expect, vi } from 'vitest';
7
- import { TuiStateManager } from '../tui-state-manager.js';
8
- import type { IExecutionResult, IToolState } from '@robota-sdk/agent-interface-transport';
9
-
10
- function makeResult(overrides?: Partial<IExecutionResult>): IExecutionResult {
11
- return {
12
- response: 'test response',
13
- history: [],
14
- toolSummaries: [],
15
- contextState: {
16
- usedPercentage: 10,
17
- remainingPercentage: 90,
18
- usedTokens: 1000,
19
- maxTokens: 200000,
20
- },
21
- ...overrides,
22
- };
23
- }
24
-
25
- describe('TuiStateManager', () => {
26
- // ── Streaming text ────────────────────────────────────────────
27
-
28
- it('accumulates streaming text on text_delta', () => {
29
- const mgr = new TuiStateManager();
30
- mgr.onTextDelta('Hello');
31
- mgr.onTextDelta(' world');
32
- expect(mgr.streamingText).toBe('Hello world');
33
- });
34
-
35
- it('clears streaming text on thinking=true', () => {
36
- const mgr = new TuiStateManager();
37
- mgr.onTextDelta('old text');
38
- mgr.onThinking(true);
39
- expect(mgr.streamingText).toBe('');
40
- });
41
-
42
- it('clears streaming text on complete', () => {
43
- const mgr = new TuiStateManager();
44
- mgr.onTextDelta('streaming');
45
- mgr.onComplete(makeResult());
46
- expect(mgr.streamingText).toBe('');
47
- });
48
-
49
- it('clears streaming text on interrupted', () => {
50
- const mgr = new TuiStateManager();
51
- mgr.onTextDelta('partial');
52
- mgr.onInterrupted();
53
- expect(mgr.streamingText).toBe('');
54
- });
55
-
56
- // ── Tool state ────────────────────────────────────────────────
57
-
58
- it('adds tool on tool_start', () => {
59
- const mgr = new TuiStateManager();
60
- const tool: IToolState = { toolName: 'Read', firstArg: 'file.ts', isRunning: true };
61
- mgr.onToolStart(tool);
62
- expect(mgr.activeTools).toHaveLength(1);
63
- expect(mgr.activeTools[0]!.toolName).toBe('Read');
64
- });
65
-
66
- it('updates tool on tool_end', () => {
67
- const mgr = new TuiStateManager();
68
- mgr.onToolStart({ toolName: 'Read', firstArg: 'file.ts', isRunning: true });
69
- mgr.onToolEnd({ toolName: 'Read', firstArg: 'file.ts', isRunning: false, result: 'success' });
70
- expect(mgr.activeTools[0]!.isRunning).toBe(false);
71
- expect(mgr.activeTools[0]!.result).toBe('success');
72
- });
73
-
74
- it('clears tools on thinking=true (next execution start)', () => {
75
- const mgr = new TuiStateManager();
76
- mgr.onToolStart({ toolName: 'Read', firstArg: '', isRunning: true });
77
- mgr.onThinking(true);
78
- expect(mgr.activeTools).toEqual([]);
79
- });
80
-
81
- it('clears tools on complete (tool summary now in messages)', () => {
82
- const mgr = new TuiStateManager();
83
- mgr.onToolStart({ toolName: 'Read', firstArg: '', isRunning: true });
84
- mgr.onComplete(makeResult());
85
- expect(mgr.activeTools).toEqual([]);
86
- });
87
-
88
- it('clears tools on interrupted (tool summary now in messages)', () => {
89
- const mgr = new TuiStateManager();
90
- mgr.onToolStart({ toolName: 'Read', firstArg: '', isRunning: true });
91
- mgr.onInterrupted();
92
- expect(mgr.activeTools).toEqual([]);
93
- });
94
-
95
- it('clears rendered history, streaming text, and active tools on explicit history clear', () => {
96
- const mgr = new TuiStateManager();
97
- mgr.addEntry({
98
- id: 'old',
99
- timestamp: new Date('2026-05-03T00:00:00.000Z'),
100
- category: 'chat',
101
- type: 'user',
102
- data: { role: 'user', content: 'old message' },
103
- });
104
- mgr.onTextDelta('partial');
105
- mgr.onToolStart({ toolName: 'Read', firstArg: 'file.ts', isRunning: true });
106
-
107
- mgr.clearHistory();
108
-
109
- expect(mgr.history).toEqual([]);
110
- expect(mgr.streamingText).toBe('');
111
- expect(mgr.activeTools).toEqual([]);
112
- });
113
-
114
- // ── Thinking state ────────────────────────────────────────────
115
-
116
- it('sets isThinking on thinking event', () => {
117
- const mgr = new TuiStateManager();
118
- mgr.onThinking(true);
119
- expect(mgr.isThinking).toBe(true);
120
- mgr.onThinking(false);
121
- expect(mgr.isThinking).toBe(false);
122
- });
123
-
124
- it('clears isAborting on thinking=false', () => {
125
- const mgr = new TuiStateManager();
126
- mgr.setAborting(true);
127
- expect(mgr.isAborting).toBe(true);
128
- mgr.onThinking(false);
129
- expect(mgr.isAborting).toBe(false);
130
- });
131
-
132
- // ── Context state ─────────────────────────────────────────────
133
-
134
- it('updates context on complete', () => {
135
- const mgr = new TuiStateManager();
136
- mgr.onComplete(
137
- makeResult({
138
- contextState: {
139
- usedPercentage: 50,
140
- remainingPercentage: 50,
141
- usedTokens: 5000,
142
- maxTokens: 10000,
143
- },
144
- }),
145
- );
146
- expect(mgr.contextState.percentage).toBe(50);
147
- expect(mgr.contextState.usedTokens).toBe(5000);
148
- });
149
-
150
- // ── Messages ──────────────────────────────────────────────────
151
-
152
- it('addMessage appends to messages', () => {
153
- const mgr = new TuiStateManager();
154
- mgr.addEntry({ role: 'user', content: 'hello' } as never);
155
- mgr.addEntry({ role: 'assistant', content: 'world' } as never);
156
- expect(mgr.history).toHaveLength(2);
157
- });
158
-
159
- it('addEntry windows to MAX_RENDERED_MESSAGES', () => {
160
- const mgr = new TuiStateManager();
161
- for (let i = 0; i < 110; i++) {
162
- mgr.addEntry({
163
- id: `${i}`,
164
- timestamp: new Date(),
165
- category: 'chat',
166
- type: 'user',
167
- data: { content: `msg ${i}` },
168
- } as never);
169
- }
170
- expect(mgr.history).toHaveLength(100);
171
- });
172
-
173
- it('syncMessages replaces all messages', () => {
174
- const mgr = new TuiStateManager();
175
- mgr.addEntry({ role: 'user', content: 'old' } as never);
176
- mgr.syncHistory([
177
- { role: 'user', content: 'new1' } as never,
178
- { role: 'assistant', content: 'new2' } as never,
179
- ]);
180
- expect(mgr.history).toHaveLength(2);
181
- expect((mgr.history[0]! as unknown as { content: string }).content).toBe('new1');
182
- });
183
-
184
- // ── onChange notification ──────────────────────────────────────
185
-
186
- it('calls onChange on every non-debounced state change', () => {
187
- const mgr = new TuiStateManager();
188
- const onChange = vi.fn();
189
- mgr.onChange = onChange;
190
-
191
- mgr.onTextDelta('hi'); // debounced — does NOT call onChange immediately
192
- mgr.onToolStart({ toolName: 'Read', firstArg: '', isRunning: true });
193
- mgr.onThinking(true); // flushes debounce timer
194
- mgr.addEntry({ role: 'user', content: 'test' } as never);
195
- mgr.setAborting(true);
196
- mgr.setPendingPrompt('queued');
197
- mgr.setContextState({ percentage: 50, usedTokens: 5000, maxTokens: 10000 });
198
-
199
- expect(onChange).toHaveBeenCalledTimes(6);
200
- });
201
-
202
- it('onTextDelta debounces notify calls', async () => {
203
- const mgr = new TuiStateManager();
204
- const onChange = vi.fn();
205
- mgr.onChange = onChange;
206
-
207
- mgr.onTextDelta('a');
208
- mgr.onTextDelta('b');
209
- mgr.onTextDelta('c');
210
-
211
- expect(onChange).toHaveBeenCalledTimes(0);
212
- expect(mgr.streamingText).toBe('abc');
213
-
214
- await new Promise((r) => setTimeout(r, 400));
215
- expect(onChange).toHaveBeenCalledTimes(1);
216
- });
217
-
218
- it('does not crash when onChange is null', () => {
219
- const mgr = new TuiStateManager();
220
- expect(() => mgr.onTextDelta('hi')).not.toThrow();
221
- });
222
-
223
- it('stores SDK execution workspace snapshots and preserves selected entry across updates', () => {
224
- const mgr = new TuiStateManager();
225
- const snapshot = {
226
- sessionId: 'session_1',
227
- selectedEntryId: 'main:session_1',
228
- updatedAt: '2026-05-09T00:00:00.000Z',
229
- entries: [
230
- {
231
- id: 'main:session_1',
232
- sourceId: 'session_1',
233
- kind: 'main_thread',
234
- origin: { kind: 'user_prompt', sessionId: 'session_1' },
235
- status: 'idle',
236
- title: 'Main thread',
237
- unread: false,
238
- attention: 'none',
239
- visibility: 'default',
240
- updatedAt: '2026-05-09T00:00:00.000Z',
241
- controls: ['select'],
242
- },
243
- {
244
- id: 'task:agent_1',
245
- sourceId: 'agent_1',
246
- kind: 'background_task',
247
- origin: { kind: 'slash_command', sessionId: 'session_1' },
248
- taskKind: 'agent',
249
- status: 'running',
250
- title: 'Explore',
251
- unread: false,
252
- attention: 'none',
253
- visibility: 'default',
254
- updatedAt: '2026-05-09T00:00:01.000Z',
255
- controls: ['select', 'cancel'],
256
- },
257
- ],
258
- } as const;
259
-
260
- mgr.syncExecutionWorkspaceSnapshot(snapshot);
261
- mgr.selectExecutionWorkspaceEntry('task:agent_1');
262
- mgr.syncExecutionWorkspaceSnapshot({ ...snapshot, updatedAt: '2026-05-09T00:00:02.000Z' });
263
-
264
- expect(mgr.selectedExecutionEntryId).toBe('task:agent_1');
265
- expect(mgr.executionWorkspaceSnapshot?.selectedEntryId).toBe('task:agent_1');
266
- });
267
-
268
- // ── Display order: Tool → Robota ──────────────────────────────
269
-
270
- it('streaming state is cleared on complete (tools moved to messages)', () => {
271
- const mgr = new TuiStateManager();
272
- mgr.onThinking(true);
273
- mgr.onTextDelta('streaming response');
274
- mgr.onToolStart({ toolName: 'Read', firstArg: 'f.ts', isRunning: true });
275
- mgr.onToolEnd({ toolName: 'Read', firstArg: 'f.ts', isRunning: false, result: 'success' });
276
-
277
- expect(mgr.streamingText).toBe('streaming response');
278
- expect(mgr.activeTools).toHaveLength(1);
279
-
280
- mgr.onComplete(makeResult());
281
-
282
- // After complete: streaming cleared, tools cleared
283
- expect(mgr.streamingText).toBe('');
284
- expect(mgr.activeTools).toEqual([]);
285
- // Tool info is now in InteractiveSession's messages (not managed here)
286
- });
287
-
288
- it('streaming state is cleared on abort (tools moved to messages)', () => {
289
- const mgr = new TuiStateManager();
290
- mgr.onThinking(true);
291
- mgr.onTextDelta('partial');
292
- mgr.onToolStart({ toolName: 'Bash', firstArg: 'ls', isRunning: true });
293
-
294
- mgr.onInterrupted();
295
-
296
- expect(mgr.streamingText).toBe('');
297
- expect(mgr.activeTools).toEqual([]);
298
- });
299
-
300
- // ── Full lifecycle: streaming → completion message order ──────
301
-
302
- it('during streaming: tools and text visible in StreamingIndicator state', () => {
303
- const mgr = new TuiStateManager();
304
-
305
- // Execution starts
306
- mgr.onThinking(true);
307
-
308
- // Tools arrive
309
- mgr.onToolStart({ toolName: 'Read', firstArg: 'file.ts', isRunning: true });
310
- mgr.onToolEnd({ toolName: 'Read', firstArg: 'file.ts', isRunning: false, result: 'success' });
311
- mgr.onToolStart({ toolName: 'Edit', firstArg: 'file.ts', isRunning: true });
312
-
313
- // Streaming text arrives
314
- mgr.onTextDelta('Here is the ');
315
- mgr.onTextDelta('result');
316
-
317
- // During streaming: both active
318
- expect(mgr.activeTools).toHaveLength(2);
319
- expect(mgr.streamingText).toBe('Here is the result');
320
- expect(mgr.isThinking).toBe(true);
321
- });
322
-
323
- it('after completion: streaming cleared, messages synced from session', () => {
324
- const mgr = new TuiStateManager();
325
-
326
- mgr.onThinking(true);
327
- mgr.onToolStart({ toolName: 'Read', firstArg: 'f.ts', isRunning: true });
328
- mgr.onToolEnd({ toolName: 'Read', firstArg: 'f.ts', isRunning: false, result: 'success' });
329
- mgr.onTextDelta('response text');
330
-
331
- // Complete event fires, then thinking ends
332
- mgr.onComplete(makeResult());
333
- mgr.onThinking(false);
334
-
335
- // StreamingIndicator state: cleared
336
- expect(mgr.streamingText).toBe('');
337
- expect(mgr.activeTools).toEqual([]);
338
- expect(mgr.isThinking).toBe(false);
339
-
340
- // Simulate InteractiveSession history entries being synced
341
- mgr.syncHistory([
342
- { id: '1', timestamp: new Date(), category: 'chat', type: 'user' } as never,
343
- { id: '2', timestamp: new Date(), category: 'event', type: 'tool-summary' } as never,
344
- { id: '3', timestamp: new Date(), category: 'chat', type: 'assistant' } as never,
345
- ]);
346
-
347
- // MessageList now has correct order: user → tool-summary → assistant
348
- expect(mgr.history).toHaveLength(3);
349
- expect(mgr.history[0]!.type).toBe('user');
350
- expect(mgr.history[1]!.type).toBe('tool-summary');
351
- expect(mgr.history[2]!.type).toBe('assistant');
352
- });
353
-
354
- it('after abort: streaming cleared, messages synced with tool → robota → system', () => {
355
- const mgr = new TuiStateManager();
356
-
357
- mgr.onThinking(true);
358
- mgr.onToolStart({ toolName: 'Bash', firstArg: 'ls', isRunning: true });
359
- mgr.onTextDelta('partial answer');
360
-
361
- mgr.onInterrupted();
362
-
363
- expect(mgr.streamingText).toBe('');
364
- expect(mgr.activeTools).toEqual([]);
365
-
366
- // Simulate InteractiveSession history entries synced after abort
367
- mgr.syncHistory([
368
- { id: '1', timestamp: new Date(), category: 'chat', type: 'user' } as never,
369
- { id: '2', timestamp: new Date(), category: 'event', type: 'tool-summary' } as never,
370
- { id: '3', timestamp: new Date(), category: 'chat', type: 'assistant' } as never,
371
- { id: '4', timestamp: new Date(), category: 'chat', type: 'system' } as never,
372
- ]);
373
-
374
- // Order: user → tool-summary → assistant → system
375
- expect(mgr.history).toHaveLength(4);
376
- expect(mgr.history[0]!.type).toBe('user');
377
- expect(mgr.history[1]!.type).toBe('tool-summary');
378
- expect(mgr.history[2]!.type).toBe('assistant');
379
- expect(mgr.history[3]!.type).toBe('system');
380
- });
381
-
382
- it('next execution clears previous tools from StreamingIndicator', () => {
383
- const mgr = new TuiStateManager();
384
-
385
- // First execution
386
- mgr.onThinking(true);
387
- mgr.onToolStart({ toolName: 'Read', firstArg: '', isRunning: true });
388
- mgr.onComplete(makeResult());
389
- expect(mgr.activeTools).toEqual([]);
390
-
391
- // Second execution starts
392
- mgr.onThinking(true);
393
- // Previous tools should not reappear
394
- expect(mgr.activeTools).toEqual([]);
395
-
396
- // New tools for second execution
397
- mgr.onToolStart({ toolName: 'Write', firstArg: 'new.ts', isRunning: true });
398
- expect(mgr.activeTools).toHaveLength(1);
399
- expect(mgr.activeTools[0]!.toolName).toBe('Write');
400
- });
401
- });
@@ -1,53 +0,0 @@
1
- import { formatExecutionWorkspaceEntryRow } from './execution-workspace-view-model.js';
2
-
3
- import type { IExecutionWorkspaceEntry } from '@robota-sdk/agent-interface-transport';
4
-
5
- export interface IBackgroundTaskRow {
6
- connector: '├' | '└';
7
- marker: '□' | '■';
8
- color: string;
9
- label: string;
10
- segments: string[];
11
- preview?: string;
12
- accessibleText: string;
13
- }
14
-
15
- export interface IBackgroundTaskRowOptions {
16
- isLast?: boolean;
17
- }
18
-
19
- export function formatBackgroundTaskRow(
20
- entry: IExecutionWorkspaceEntry,
21
- options: IBackgroundTaskRowOptions = {},
22
- ): IBackgroundTaskRow {
23
- const row = formatExecutionWorkspaceEntryRow(entry);
24
- const marker = isActiveEntry(entry) ? '□' : '■';
25
- const segments = [row.statusLabel, row.subtitle].filter(
26
- (segment): segment is string => typeof segment === 'string' && segment.length > 0,
27
- );
28
- return {
29
- connector: options.isLast === false ? '├' : '└',
30
- marker,
31
- color: row.color,
32
- label: row.title,
33
- segments,
34
- preview: row.preview,
35
- accessibleText: [
36
- `${options.isLast === false ? '├' : '└'} ${marker} ${row.title}`,
37
- ...segments,
38
- row.preview,
39
- ]
40
- .filter((part): part is string => typeof part === 'string' && part.length > 0)
41
- .join(' · '),
42
- };
43
- }
44
-
45
- function isActiveEntry(entry: IExecutionWorkspaceEntry): boolean {
46
- return (
47
- entry.status === 'active' ||
48
- entry.status === 'queued' ||
49
- entry.status === 'running' ||
50
- entry.status === 'waiting_permission' ||
51
- entry.status === 'sleeping'
52
- );
53
- }
@@ -1,9 +0,0 @@
1
- export type {
2
- TOnMissingArgsAction,
3
- ITuiPickerItem,
4
- ITuiCommandInteraction,
5
- ITuiPickerInteraction,
6
- ITuiConfirmInteraction,
7
- TAnyTuiCommandInteraction,
8
- } from '@robota-sdk/agent-interface-tui';
9
- export { isPickerInteraction, isConfirmInteraction } from '@robota-sdk/agent-interface-tui';
@@ -1,122 +0,0 @@
1
- import type { TUniversalValue } from '@robota-sdk/agent-core';
2
-
3
- const MAX_PREVIEW_LINES = 4;
4
- const SUCCESS_EXIT_CODE = 0;
5
- const COMMAND_TOOL_NAMES = new Set(['Bash', 'BackgroundProcess']);
6
-
7
- export interface ICommandOutputInput {
8
- toolName: string;
9
- firstArg?: string;
10
- isRunning?: boolean;
11
- result?: string;
12
- toolResultData?: string;
13
- }
14
-
15
- export interface ICommandOutputSummary {
16
- status: 'success' | 'error';
17
- statusLabel: string;
18
- previewLines: string[];
19
- omittedLineCount: number;
20
- transcriptHint?: string;
21
- }
22
-
23
- export function formatCommandOutputSummary(
24
- tool: ICommandOutputInput,
25
- ): ICommandOutputSummary | undefined {
26
- if (!COMMAND_TOOL_NAMES.has(tool.toolName) || !tool.toolResultData) return undefined;
27
-
28
- const parsed = parseToolResultData(tool.toolResultData);
29
- const exitCode = getNumberValue(parsed, 'exitCode');
30
- const successValue = getBooleanValue(parsed, 'success');
31
- const output = buildOutputText(tool.toolResultData, parsed);
32
- const lines = trimTrailingBlankLines(splitOutputLines(output));
33
- const previewLines = lines.slice(0, MAX_PREVIEW_LINES);
34
- const omittedLineCount = Math.max(0, lines.length - previewLines.length);
35
- const isFailed =
36
- tool.result === 'error' ||
37
- successValue === false ||
38
- (exitCode !== undefined && exitCode !== SUCCESS_EXIT_CODE);
39
-
40
- return {
41
- status: isFailed ? 'error' : 'success',
42
- statusLabel: formatStatusLabel(isFailed, exitCode),
43
- previewLines,
44
- omittedLineCount,
45
- transcriptHint:
46
- omittedLineCount > 0
47
- ? `... +${omittedLineCount} lines (full output in session transcript)`
48
- : undefined,
49
- };
50
- }
51
-
52
- function parseToolResultData(value: string): TUniversalValue {
53
- try {
54
- return JSON.parse(value) as TUniversalValue;
55
- } catch {
56
- return value;
57
- }
58
- }
59
-
60
- function buildOutputText(raw: string, parsed: TUniversalValue): string {
61
- if (!isUniversalObject(parsed)) return raw;
62
-
63
- const output = getStringValue(parsed, 'output');
64
- if (output !== undefined) return output;
65
-
66
- const stdout = getStringValue(parsed, 'stdout');
67
- const stderr = getStringValue(parsed, 'stderr');
68
- const error = getStringValue(parsed, 'error');
69
- const lines: string[] = [];
70
- if (stdout) lines.push(stdout);
71
- if (stderr) lines.push(prefixLines(stderr, '[stderr] '));
72
- if (!stdout && !stderr && error) lines.push(error);
73
- return lines.join('\n');
74
- }
75
-
76
- function formatStatusLabel(isFailed: boolean, exitCode: number | undefined): string {
77
- if (exitCode !== undefined && exitCode !== SUCCESS_EXIT_CODE) return `exit ${exitCode}`;
78
- return isFailed ? 'error' : 'ok';
79
- }
80
-
81
- function splitOutputLines(output: string): string[] {
82
- if (!output) return [];
83
- return output.replace(/\r\n/g, '\n').split('\n');
84
- }
85
-
86
- function trimTrailingBlankLines(lines: string[]): string[] {
87
- let end = lines.length;
88
- while (end > 0 && lines[end - 1]!.trim().length === 0) {
89
- end -= 1;
90
- }
91
- return lines.slice(0, end);
92
- }
93
-
94
- function prefixLines(value: string, prefix: string): string {
95
- return splitOutputLines(value)
96
- .map((line) => `${prefix}${line}`)
97
- .join('\n');
98
- }
99
-
100
- function isUniversalObject(value: TUniversalValue): value is Record<string, TUniversalValue> {
101
- return (
102
- typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)
103
- );
104
- }
105
-
106
- function getStringValue(source: TUniversalValue, key: string): string | undefined {
107
- if (!isUniversalObject(source)) return undefined;
108
- const value = source[key];
109
- return typeof value === 'string' ? value : undefined;
110
- }
111
-
112
- function getNumberValue(source: TUniversalValue, key: string): number | undefined {
113
- if (!isUniversalObject(source)) return undefined;
114
- const value = source[key];
115
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
116
- }
117
-
118
- function getBooleanValue(source: TUniversalValue, key: string): boolean | undefined {
119
- if (!isUniversalObject(source)) return undefined;
120
- const value = source[key];
121
- return typeof value === 'boolean' ? value : undefined;
122
- }
@@ -1,41 +0,0 @@
1
- import { findProviderDefinition } from '@robota-sdk/agent-core';
2
- import type { IProviderDefinition } from '@robota-sdk/agent-core';
3
- import type { CommandRegistry } from '@robota-sdk/agent-framework';
4
- import {
5
- applyActiveModelChange,
6
- applyStatusLineSettings,
7
- deleteSettings,
8
- getUserSettingsPath,
9
- readSettings,
10
- resolveGitBranch,
11
- writeSettings,
12
- } from '@robota-sdk/agent-framework';
13
- import type { ITuiCliAdapter } from './tui-cli-adapter.js';
14
-
15
- export interface IDefaultTuiCliAdapterOptions {
16
- providerDefinitions: readonly IProviderDefinition[];
17
- reloadPluginCommandSource: (registry: CommandRegistry) => void;
18
- }
19
-
20
- export function createDefaultTuiCliAdapter({
21
- providerDefinitions,
22
- reloadPluginCommandSource,
23
- }: IDefaultTuiCliAdapterOptions): ITuiCliAdapter {
24
- return {
25
- getUserSettingsPath: () => getUserSettingsPath(),
26
- readSettings: (path) => readSettings(path),
27
- writeSettings: (path, settings) => writeSettings(path, settings),
28
- deleteSettings: (path) => deleteSettings(path),
29
- applyStatusLineSettings: (path, patch) => applyStatusLineSettings(path, patch),
30
- reloadPluginCommandSource: (registry) => {
31
- reloadPluginCommandSource(registry);
32
- },
33
- applyActiveModelChange: (cwd, modelId, options) => {
34
- applyActiveModelChange(cwd, modelId, options);
35
- return { applied: true };
36
- },
37
- getGitBranch: (cwd) => resolveGitBranch(cwd),
38
- getProviderDisplayName: (type) =>
39
- findProviderDefinition(providerDefinitions, type)?.displayName ?? type,
40
- };
41
- }