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

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 (187) hide show
  1. package/README.md +10 -10
  2. package/dist/node/headless/index.cjs +1 -1
  3. package/dist/node/{headless-CT2ibQnr.cjs → headless-OnpVk4-k.cjs} +7 -7
  4. package/dist/node/index.cjs +1 -1
  5. package/dist/node/index.d.ts +1 -6
  6. package/dist/node/index.d.ts.map +1 -1
  7. package/dist/node/index.js +1 -1
  8. package/dist/node/index.js.map +1 -1
  9. package/package.json +7 -75
  10. package/src/index.ts +1 -5
  11. package/src/transport-registry.ts +0 -9
  12. package/dist/node/http/index.cjs +0 -1
  13. package/dist/node/http/index.d.ts +0 -2
  14. package/dist/node/http/index.js +0 -1
  15. package/dist/node/http-2Jiuflc1.js +0 -2
  16. package/dist/node/http-2Jiuflc1.js.map +0 -1
  17. package/dist/node/http-CBAvefLw.cjs +0 -1
  18. package/dist/node/index-BNccqSpv.d.ts +0 -86
  19. package/dist/node/index-BNccqSpv.d.ts.map +0 -1
  20. package/dist/node/index-BUhHIf7X.d.ts +0 -86
  21. package/dist/node/index-BUhHIf7X.d.ts.map +0 -1
  22. package/dist/node/index-BnAGE-u9.d.ts +0 -33
  23. package/dist/node/index-BnAGE-u9.d.ts.map +0 -1
  24. package/dist/node/index-BrQ4gGw0.d.ts +0 -213
  25. package/dist/node/index-BrQ4gGw0.d.ts.map +0 -1
  26. package/dist/node/index-CoeBF21y.d.ts +0 -213
  27. package/dist/node/index-CoeBF21y.d.ts.map +0 -1
  28. package/dist/node/index-DHt-2VQ-.d.ts +0 -46
  29. package/dist/node/index-DHt-2VQ-.d.ts.map +0 -1
  30. package/dist/node/index-DMwKN5Le.d.ts +0 -33
  31. package/dist/node/index-DMwKN5Le.d.ts.map +0 -1
  32. package/dist/node/index-c0M42fsA.d.ts +0 -46
  33. package/dist/node/index-c0M42fsA.d.ts.map +0 -1
  34. package/dist/node/mcp/index.cjs +0 -1
  35. package/dist/node/mcp/index.d.ts +0 -2
  36. package/dist/node/mcp/index.js +0 -1
  37. package/dist/node/mcp-BOglBJNy.cjs +0 -1
  38. package/dist/node/mcp-D3BBVK7C.js +0 -2
  39. package/dist/node/mcp-D3BBVK7C.js.map +0 -1
  40. package/dist/node/rolldown-runtime-CMqjfN_6.cjs +0 -1
  41. package/dist/node/tui/index.cjs +0 -1
  42. package/dist/node/tui/index.d.ts +0 -2
  43. package/dist/node/tui/index.js +0 -1
  44. package/dist/node/tui-CcH5EsQh.js +0 -25
  45. package/dist/node/tui-CcH5EsQh.js.map +0 -1
  46. package/dist/node/tui-DznRbcku.cjs +0 -24
  47. package/dist/node/ws/index.cjs +0 -1
  48. package/dist/node/ws/index.d.ts +0 -2
  49. package/dist/node/ws/index.js +0 -1
  50. package/dist/node/ws-Dc2RUwVs.js +0 -2
  51. package/dist/node/ws-Dc2RUwVs.js.map +0 -1
  52. package/dist/node/ws-QNMQn5kg.cjs +0 -1
  53. package/src/http/__tests__/http-transport.test.ts +0 -55
  54. package/src/http/__tests__/routes.test.ts +0 -168
  55. package/src/http/http-transport.ts +0 -41
  56. package/src/http/index.ts +0 -4
  57. package/src/http/routes.ts +0 -152
  58. package/src/mcp/__tests__/mcp-server.test.ts +0 -66
  59. package/src/mcp/__tests__/mcp-transport.test.ts +0 -46
  60. package/src/mcp/index.ts +0 -4
  61. package/src/mcp/mcp-server.ts +0 -163
  62. package/src/mcp/mcp-transport.ts +0 -48
  63. package/src/tui/App.tsx +0 -491
  64. package/src/tui/BackgroundTaskPanel.tsx +0 -36
  65. package/src/tui/CjkTextInput.tsx +0 -199
  66. package/src/tui/ConfirmPrompt.tsx +0 -70
  67. package/src/tui/ContextWarningBanner.tsx +0 -34
  68. package/src/tui/ExecutionWorkspaceDetailPane.tsx +0 -64
  69. package/src/tui/ExecutionWorkspaceSwitcher.tsx +0 -187
  70. package/src/tui/InputArea.tsx +0 -310
  71. package/src/tui/InteractivePrompt.tsx +0 -59
  72. package/src/tui/ListPicker.tsx +0 -95
  73. package/src/tui/MenuSelect.tsx +0 -104
  74. package/src/tui/MessageList.tsx +0 -284
  75. package/src/tui/PermissionPrompt.tsx +0 -86
  76. package/src/tui/PluginTUI.tsx +0 -258
  77. package/src/tui/SessionPicker.tsx +0 -68
  78. package/src/tui/SessionStatusBar.tsx +0 -73
  79. package/src/tui/SlashAutocomplete.tsx +0 -110
  80. package/src/tui/StatusBar.tsx +0 -236
  81. package/src/tui/StreamingIndicator.tsx +0 -93
  82. package/src/tui/TextPrompt.tsx +0 -81
  83. package/src/tui/ToolCommandOutput.tsx +0 -39
  84. package/src/tui/ToolDiffBlock.tsx +0 -32
  85. package/src/tui/TransportTUI.tsx +0 -117
  86. package/src/tui/TuiInteractionChannel.ts +0 -495
  87. package/src/tui/UpdateNotice.tsx +0 -14
  88. package/src/tui/UsageSummaryEntry.tsx +0 -39
  89. package/src/tui/WaveText.tsx +0 -44
  90. package/src/tui/__tests__/InteractivePrompt.test.tsx +0 -82
  91. package/src/tui/__tests__/ListPicker.test.tsx +0 -159
  92. package/src/tui/__tests__/MenuSelect.test.tsx +0 -103
  93. package/src/tui/__tests__/PluginTUI.test.tsx +0 -167
  94. package/src/tui/__tests__/SlashAutocomplete.test.tsx +0 -140
  95. package/src/tui/__tests__/TextPrompt.test.tsx +0 -98
  96. package/src/tui/__tests__/TuiInteractionChannel.display-contract.test.ts +0 -239
  97. package/src/tui/__tests__/TuiInteractionChannel.lifecycle.test.ts +0 -297
  98. package/src/tui/__tests__/TuiInteractionChannel.requestAction.test.ts +0 -124
  99. package/src/tui/__tests__/UpdateNotice.test.tsx +0 -15
  100. package/src/tui/__tests__/abort-after-permission.test.tsx +0 -169
  101. package/src/tui/__tests__/abort-streaming-e2e.test.tsx +0 -183
  102. package/src/tui/__tests__/background-task-panel.test.tsx +0 -53
  103. package/src/tui/__tests__/background-task-row-format.test.ts +0 -59
  104. package/src/tui/__tests__/channel-factory-integration.test.ts +0 -138
  105. package/src/tui/__tests__/cjk-text-input-flow.test.ts +0 -109
  106. package/src/tui/__tests__/cjk-text-input.test.ts +0 -191
  107. package/src/tui/__tests__/command-effect-handler.test.ts +0 -127
  108. package/src/tui/__tests__/command-output-summary.test.ts +0 -95
  109. package/src/tui/__tests__/compact-event-bridge.test.ts +0 -20
  110. package/src/tui/__tests__/confirm-permission-flow.test.ts +0 -130
  111. package/src/tui/__tests__/confirm-prompt.test.tsx +0 -87
  112. package/src/tui/__tests__/execution-workspace-switcher.test.tsx +0 -110
  113. package/src/tui/__tests__/execution-workspace-view-model.test.ts +0 -93
  114. package/src/tui/__tests__/fixtures/provider-setup-prompt-driver.tsx +0 -125
  115. package/src/tui/__tests__/input-area-flow.test.ts +0 -164
  116. package/src/tui/__tests__/message-list-rendering.test.tsx +0 -353
  117. package/src/tui/__tests__/prompt-queue.test.tsx +0 -255
  118. package/src/tui/__tests__/provider-setup-pty-e2e.test.ts +0 -233
  119. package/src/tui/__tests__/pty/pty-driver.ts +0 -135
  120. package/src/tui/__tests__/pty/tui-pty.ptytest.ts +0 -61
  121. package/src/tui/__tests__/render-channel-options.test.ts +0 -32
  122. package/src/tui/__tests__/render-markdown.test.ts +0 -72
  123. package/src/tui/__tests__/selection-flow.test.ts +0 -61
  124. package/src/tui/__tests__/session-init-poller.test.ts +0 -102
  125. package/src/tui/__tests__/session-naming.test.ts +0 -64
  126. package/src/tui/__tests__/session-switch-channel.test.tsx +0 -307
  127. package/src/tui/__tests__/slash-routing-effects.test.ts +0 -228
  128. package/src/tui/__tests__/status-activity.test.ts +0 -71
  129. package/src/tui/__tests__/status-bar.test.tsx +0 -177
  130. package/src/tui/__tests__/streaming-indicator.test.tsx +0 -137
  131. package/src/tui/__tests__/text-prompt-flow.test.ts +0 -77
  132. package/src/tui/__tests__/tui-channel-init-failure.test.ts +0 -57
  133. package/src/tui/__tests__/tui-state-manager.test.ts +0 -401
  134. package/src/tui/background-task-row-format.ts +0 -53
  135. package/src/tui/command-interaction.ts +0 -9
  136. package/src/tui/command-output-summary.ts +0 -122
  137. package/src/tui/create-default-tui-cli-adapter.ts +0 -41
  138. package/src/tui/execution-workspace-view-model.ts +0 -123
  139. package/src/tui/flows/cjk-text-input-flow.ts +0 -285
  140. package/src/tui/flows/confirm-prompt-flow.ts +0 -45
  141. package/src/tui/flows/input-area-flow.ts +0 -189
  142. package/src/tui/flows/permission-prompt-flow.ts +0 -85
  143. package/src/tui/flows/selection-flow.ts +0 -126
  144. package/src/tui/flows/session-init-poller.ts +0 -77
  145. package/src/tui/flows/text-prompt-flow.ts +0 -98
  146. package/src/tui/hooks/command-effect-handler.ts +0 -97
  147. package/src/tui/hooks/command-effect-queue.ts +0 -39
  148. package/src/tui/hooks/side-effects-types.ts +0 -35
  149. package/src/tui/hooks/useAutocomplete.ts +0 -87
  150. package/src/tui/hooks/usePluginCallbacks.ts +0 -31
  151. package/src/tui/hooks/usePluginScreenData.ts +0 -85
  152. package/src/tui/hooks/useSideEffects.ts +0 -175
  153. package/src/tui/hooks/useSlashRouting.ts +0 -118
  154. package/src/tui/hooks/useStatusLineSettings.ts +0 -37
  155. package/src/tui/hooks/useTuiChannel.ts +0 -95
  156. package/src/tui/index.ts +0 -14
  157. package/src/tui/interactions/CommandConfirm.tsx +0 -36
  158. package/src/tui/interactions/CommandPicker.tsx +0 -77
  159. package/src/tui/interactions/__tests__/CommandConfirm.test.tsx +0 -124
  160. package/src/tui/interactions/__tests__/CommandPicker.test.tsx +0 -138
  161. package/src/tui/plugin-tui-handlers.ts +0 -163
  162. package/src/tui/render-markdown.ts +0 -130
  163. package/src/tui/render.tsx +0 -129
  164. package/src/tui/session-naming.ts +0 -33
  165. package/src/tui/status-activity.ts +0 -63
  166. package/src/tui/tui-cli-adapter-context.tsx +0 -13
  167. package/src/tui/tui-cli-adapter.ts +0 -25
  168. package/src/tui/tui-state-manager.ts +0 -226
  169. package/src/tui/tui-transport.ts +0 -35
  170. package/src/tui/types.ts +0 -15
  171. package/src/tui/utils/__tests__/edit-diff.test.ts +0 -426
  172. package/src/tui/utils/__tests__/paste-detection.test.ts +0 -116
  173. package/src/tui/utils/__tests__/paste-labels.test.ts +0 -46
  174. package/src/tui/utils/__tests__/tool-call-extractor.test.ts +0 -227
  175. package/src/tui/utils/__tests__/tool-diff-summary.test.ts +0 -104
  176. package/src/tui/utils/edit-diff.ts +0 -153
  177. package/src/tui/utils/paste-labels.ts +0 -9
  178. package/src/tui/utils/tool-call-extractor.ts +0 -92
  179. package/src/tui/utils/tool-diff-summary.ts +0 -75
  180. package/src/ws/__tests__/ws-handler.test.ts +0 -409
  181. package/src/ws/__tests__/ws-transport.test.ts +0 -53
  182. package/src/ws/index.ts +0 -13
  183. package/src/ws/ws-background-messages.ts +0 -170
  184. package/src/ws/ws-handler.ts +0 -280
  185. package/src/ws/ws-protocol.ts +0 -78
  186. package/src/ws/ws-transport-configurable.ts +0 -128
  187. 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
- }