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