@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,228 +0,0 @@
1
- import { homedir } from 'node:os';
2
- import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- import { describe, expect, it } from 'vitest';
6
- import { afterEach, vi } from 'vitest';
7
- import {
8
- CommandRegistry,
9
- BundlePluginLoader,
10
- PluginCommandSource,
11
- } from '@robota-sdk/agent-framework';
12
- import type {
13
- ICommandInteraction,
14
- IInteractiveSession,
15
- } from '@robota-sdk/agent-interface-transport';
16
- import { TuiStateManager } from '../tui-state-manager.js';
17
- import { applySystemCommandResult } from '../hooks/useSlashRouting.js';
18
- import { CommandEffectQueue } from '../hooks/command-effect-queue.js';
19
-
20
- const PLUGIN_SOURCE_NAME = 'plugin';
21
-
22
- function reloadPluginCommandSource(registry: CommandRegistry): void {
23
- const pluginsDir = join(process.env.HOME ?? homedir(), '.robota', 'plugins');
24
- const loader = new BundlePluginLoader(pluginsDir);
25
- try {
26
- // allow-fallback: test helper — empty registry on load error is safe
27
- const plugins = loader.loadPluginsSync();
28
- if (plugins.length === 0) {
29
- registry.replaceSource(PLUGIN_SOURCE_NAME);
30
- } else {
31
- registry.replaceSource(PLUGIN_SOURCE_NAME, new PluginCommandSource(plugins));
32
- }
33
- } catch {
34
- // allow-fallback: test helper — empty registry on load error is safe
35
- registry.replaceSource(PLUGIN_SOURCE_NAME);
36
- }
37
- }
38
-
39
- describe('applySystemCommandResult', () => {
40
- afterEach(() => {
41
- vi.unstubAllEnvs();
42
- });
43
-
44
- function createRegistry(): CommandRegistry {
45
- return new CommandRegistry();
46
- }
47
-
48
- function legacyCommandField(suffix: 'Interaction' | 'Effects'): string {
49
- return `_pendingCommand${suffix}`;
50
- }
51
-
52
- it('stores statusline settings patch as a CLI side effect', () => {
53
- const session = {
54
- getContextState: () => ({ usedPercentage: 0, usedTokens: 0, maxTokens: 0 }),
55
- } as unknown as IInteractiveSession;
56
- const manager = new TuiStateManager();
57
- const queue = new CommandEffectQueue();
58
-
59
- applySystemCommandResult(
60
- {
61
- success: true,
62
- message: 'Status line disabled.',
63
- effects: [{ type: 'statusline-settings-patch', patch: { enabled: false } }],
64
- },
65
- session,
66
- createRegistry(),
67
- manager,
68
- queue,
69
- );
70
-
71
- expect(queue.drain()).toEqual({
72
- type: 'effects',
73
- effects: [{ type: 'statusline-settings-patch', patch: { enabled: false } }],
74
- });
75
- expect(Object.hasOwn(session, legacyCommandField('Effects'))).toBe(false);
76
- });
77
-
78
- it('stores generic command interactions without interpreting command-specific data', () => {
79
- const session = {
80
- getContextState: () => ({ usedPercentage: 0, usedTokens: 0, maxTokens: 0 }),
81
- } as unknown as IInteractiveSession;
82
- const manager = new TuiStateManager();
83
- const queue = new CommandEffectQueue();
84
- const interaction: ICommandInteraction = {
85
- prompt: {
86
- kind: 'choice',
87
- title: 'Change provider?',
88
- options: [
89
- { value: 'yes', label: 'Yes' },
90
- { value: 'no', label: 'No' },
91
- ],
92
- },
93
- submit: () => ({ success: true, message: 'done' }),
94
- };
95
-
96
- applySystemCommandResult(
97
- {
98
- success: true,
99
- message: 'Switch provider?',
100
- interaction,
101
- },
102
- session,
103
- createRegistry(),
104
- manager,
105
- queue,
106
- );
107
-
108
- expect(queue.drain()).toEqual({ type: 'interaction', interaction });
109
- expect(Object.hasOwn(session, legacyCommandField('Interaction'))).toBe(false);
110
- });
111
-
112
- it('stores host command side effects', () => {
113
- const session = {
114
- getContextState: () => ({ usedPercentage: 0, usedTokens: 0, maxTokens: 0 }),
115
- } as unknown as IInteractiveSession;
116
- const manager = new TuiStateManager();
117
- const queue = new CommandEffectQueue();
118
-
119
- applySystemCommandResult(
120
- {
121
- success: true,
122
- message: 'Opening plugin manager...',
123
- effects: [{ type: 'plugin-tui-requested' }],
124
- },
125
- session,
126
- createRegistry(),
127
- manager,
128
- queue,
129
- );
130
-
131
- expect(queue.drain()).toEqual({
132
- type: 'effects',
133
- effects: [{ type: 'plugin-tui-requested' }],
134
- });
135
- expect(Object.hasOwn(session, legacyCommandField('Effects'))).toBe(false);
136
- });
137
-
138
- it('applies conversation history clearing immediately before adding the command result', () => {
139
- const session = {
140
- getContextState: () => ({ usedPercentage: 0, usedTokens: 0, maxTokens: 0 }),
141
- } as unknown as IInteractiveSession;
142
- const manager = new TuiStateManager();
143
- const queue = new CommandEffectQueue();
144
- manager.addEntry({
145
- id: 'old',
146
- timestamp: new Date('2026-05-03T00:00:00.000Z'),
147
- category: 'chat',
148
- type: 'user',
149
- data: { role: 'user', content: 'old message' },
150
- });
151
-
152
- applySystemCommandResult(
153
- {
154
- success: true,
155
- message: 'Conversation cleared.',
156
- effects: [{ type: 'conversation-history-cleared' }],
157
- },
158
- session,
159
- createRegistry(),
160
- manager,
161
- queue,
162
- );
163
-
164
- expect(manager.history).toHaveLength(1);
165
- expect(manager.history[0]?.type).toBe('system');
166
- expect(manager.history[0]?.data).toMatchObject({ content: 'Conversation cleared.' });
167
- expect(queue.drain()).toBeUndefined();
168
- expect(Object.hasOwn(session, legacyCommandField('Effects'))).toBe(false);
169
- });
170
-
171
- it('reloads plugin command source immediately when requested', () => {
172
- const home = mkdtempSync(join(tmpdir(), 'robota-plugin-reload-'));
173
- const pluginDir = join(
174
- home,
175
- '.robota',
176
- 'plugins',
177
- 'cache',
178
- 'community',
179
- 'fresh-plugin',
180
- '1.0.0',
181
- );
182
- mkdirSync(join(pluginDir, '.claude-plugin'), { recursive: true });
183
- mkdirSync(join(pluginDir, 'skills', 'fresh-skill'), { recursive: true });
184
- writeFileSync(
185
- join(pluginDir, '.claude-plugin', 'plugin.json'),
186
- JSON.stringify({
187
- name: 'fresh-plugin',
188
- version: '1.0.0',
189
- description: 'Fresh plugin',
190
- features: { skills: true },
191
- }),
192
- 'utf8',
193
- );
194
- writeFileSync(
195
- join(pluginDir, 'skills', 'fresh-skill', 'SKILL.md'),
196
- '---\ndescription: Fresh skill\n---\n# Fresh Skill\n',
197
- 'utf8',
198
- );
199
- vi.stubEnv('HOME', home);
200
- const session = {
201
- getContextState: () => ({ usedPercentage: 0, usedTokens: 0, maxTokens: 0 }),
202
- } as unknown as IInteractiveSession;
203
- const registry = createRegistry();
204
- const queue = new CommandEffectQueue();
205
- registry.addSource({
206
- name: 'plugin',
207
- getCommands: () => [{ name: 'stale-skill', description: 'Stale', source: 'plugin' }],
208
- });
209
- const manager = new TuiStateManager();
210
-
211
- applySystemCommandResult(
212
- {
213
- success: true,
214
- message: 'Reloaded 1 plugin resource.',
215
- effects: [{ type: 'plugin-registry-reload-requested' }],
216
- },
217
- session,
218
- registry,
219
- manager,
220
- queue,
221
- reloadPluginCommandSource,
222
- );
223
-
224
- expect(registry.getCommands().map((command) => command.name)).toEqual(['fresh-skill']);
225
- expect(queue.drain()).toBeUndefined();
226
- expect(Object.hasOwn(session, legacyCommandField('Effects'))).toBe(false);
227
- });
228
- });
@@ -1,71 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { formatStatusActivity } from '../status-activity.js';
3
-
4
- describe('formatStatusActivity', () => {
5
- it('prioritizes running tools over thinking, background work, and queued prompts', () => {
6
- const activity = formatStatusActivity({
7
- isThinking: true,
8
- activeToolCount: 2,
9
- activeBackgroundTaskCount: 3,
10
- hasPendingPrompt: true,
11
- });
12
-
13
- expect(activity.kind).toBe('tools');
14
- expect(activity.label).toBe('Tools (2)');
15
- expect(activity.color).toBe('cyan');
16
- expect(activity.segments).toEqual(['queued']);
17
- expect(activity.text).toBe('Tools (2) · queued');
18
- });
19
-
20
- it('shows thinking as the primary model waiting state', () => {
21
- const activity = formatStatusActivity({
22
- isThinking: true,
23
- activeToolCount: 0,
24
- activeBackgroundTaskCount: 0,
25
- hasPendingPrompt: false,
26
- });
27
-
28
- expect(activity.kind).toBe('thinking');
29
- expect(activity.label).toBe('Thinking');
30
- expect(activity.segments).toEqual([]);
31
- });
32
-
33
- it('shows background activity when foreground work is idle', () => {
34
- const activity = formatStatusActivity({
35
- isThinking: false,
36
- activeToolCount: 0,
37
- activeBackgroundTaskCount: 1,
38
- hasPendingPrompt: false,
39
- });
40
-
41
- expect(activity.kind).toBe('background');
42
- expect(activity.label).toBe('Background (1)');
43
- expect(activity.color).toBe('cyan');
44
- });
45
-
46
- it('shows queued prompt before idle when no work is active', () => {
47
- const activity = formatStatusActivity({
48
- isThinking: false,
49
- activeToolCount: 0,
50
- activeBackgroundTaskCount: 0,
51
- hasPendingPrompt: true,
52
- });
53
-
54
- expect(activity.kind).toBe('queued');
55
- expect(activity.label).toBe('Queued');
56
- expect(activity.color).toBe('yellow');
57
- });
58
-
59
- it('keeps idle compact and dim', () => {
60
- const activity = formatStatusActivity({
61
- isThinking: false,
62
- activeToolCount: 0,
63
- activeBackgroundTaskCount: 0,
64
- hasPendingPrompt: false,
65
- });
66
-
67
- expect(activity.kind).toBe('idle');
68
- expect(activity.text).toBe('Idle');
69
- expect(activity.color).toBe('gray');
70
- });
71
- });
@@ -1,177 +0,0 @@
1
- import React from 'react';
2
- import { render } from 'ink-testing-library';
3
- import { describe, it, expect } from 'vitest';
4
- import StatusBar from '../StatusBar.js';
5
-
6
- describe('StatusBar', () => {
7
- const baseProps = {
8
- permissionMode: 'default' as const,
9
- modelName: 'test-model',
10
- sessionId: 'sess-1',
11
- isThinking: false,
12
- activeToolCount: 0,
13
- activeBackgroundTaskCount: 0,
14
- hasPendingPrompt: false,
15
- contextPercentage: 10,
16
- contextUsedTokens: 1000,
17
- contextMaxTokens: 200000,
18
- };
19
-
20
- it('renders without session name', () => {
21
- const { lastFrame } = render(<StatusBar {...baseProps} />);
22
- const frame = lastFrame()!;
23
- expect(frame).toContain('test-model');
24
- expect(frame).not.toContain('Mode: default');
25
- });
26
-
27
- it('hides default permission mode', () => {
28
- const { lastFrame } = render(<StatusBar {...baseProps} permissionMode="default" />);
29
- const frame = lastFrame()!;
30
- expect(frame).not.toContain('Mode:');
31
- expect(frame).not.toContain('default');
32
- });
33
-
34
- it('shows non-default permission modes', () => {
35
- for (const permissionMode of ['plan', 'acceptEdits', 'bypassPermissions'] as const) {
36
- const { lastFrame, unmount } = render(
37
- <StatusBar {...baseProps} permissionMode={permissionMode} />,
38
- );
39
- const frame = lastFrame()!;
40
- expect(frame).toContain('Mode:');
41
- expect(frame).toContain(permissionMode);
42
- unmount();
43
- }
44
- });
45
-
46
- it('renders session name when provided', () => {
47
- const { lastFrame } = render(<StatusBar {...baseProps} sessionName="my-feature" />);
48
- const frame = lastFrame()!;
49
- expect(frame).toContain('my-feature');
50
- });
51
-
52
- it('does not show session name when undefined', () => {
53
- const { lastFrame } = render(<StatusBar {...baseProps} sessionName={undefined} />);
54
- const frame = lastFrame()!;
55
- // Should not have extra separator for missing name
56
- expect(frame).not.toContain('my-feature');
57
- });
58
-
59
- it('renders model name', () => {
60
- const { lastFrame } = render(<StatusBar {...baseProps} />);
61
- const frame = lastFrame()!;
62
- expect(frame).toContain('test-model');
63
- });
64
-
65
- it('renders provider display name and model when provided', () => {
66
- const { lastFrame } = render(<StatusBar {...baseProps} providerDisplayName="Anthropic" />);
67
- const frame = lastFrame()!;
68
- expect(frame).toContain('Anthropic');
69
- expect(frame).toContain('test-model');
70
- });
71
-
72
- it('does not render message count in the status bar', () => {
73
- const { lastFrame } = render(<StatusBar {...baseProps} />);
74
- const frame = lastFrame()!;
75
- expect(frame).not.toContain('msgs:');
76
- });
77
-
78
- it('shows thinking indicator when isThinking is true', () => {
79
- const { lastFrame } = render(<StatusBar {...baseProps} isThinking={true} />);
80
- const frame = lastFrame()!;
81
- expect(frame).not.toContain('Activity:');
82
- expect(frame).toContain('Thinking');
83
- expect(frame.indexOf('Thinking')).toBeLessThan(frame.indexOf('test-model'));
84
- });
85
-
86
- it('does not duplicate thinking state in secondary status text', () => {
87
- const { lastFrame } = render(<StatusBar {...baseProps} isThinking={true} />);
88
- const frame = lastFrame()!;
89
- expect(frame).toContain('Thinking');
90
- expect(frame).not.toContain('thinking...');
91
- expect(frame).not.toContain('msgs:');
92
- });
93
-
94
- it('hides the lower-right prompt-processing indicator while idle', () => {
95
- const { lastFrame } = render(<StatusBar {...baseProps} isThinking={false} />);
96
- const frame = lastFrame()!;
97
- expect(frame).not.toContain('thinking...');
98
- });
99
-
100
- it('prioritizes tool activity in the primary scan path', () => {
101
- const { lastFrame } = render(
102
- <StatusBar
103
- {...baseProps}
104
- isThinking={true}
105
- activeToolCount={2}
106
- activeBackgroundTaskCount={1}
107
- hasPendingPrompt={true}
108
- />,
109
- );
110
- const frame = lastFrame()!;
111
- expect(frame).not.toContain('Activity:');
112
- expect(frame).toContain('Tools (2)');
113
- expect(frame).not.toContain('Tools x2');
114
- expect(frame).toContain('queued');
115
- expect(frame).not.toContain('thinking...');
116
- expect(frame.indexOf('Tools (2)')).toBeLessThan(frame.indexOf('test-model'));
117
- expect(frame).not.toContain('Thinking...');
118
- });
119
-
120
- it('shows background activity when no foreground execution is active', () => {
121
- const { lastFrame } = render(<StatusBar {...baseProps} activeBackgroundTaskCount={3} />);
122
- const frame = lastFrame()!;
123
- expect(frame).toContain('Background (3)');
124
- expect(frame.indexOf('Background (3)')).toBeLessThan(frame.indexOf('test-model'));
125
- });
126
-
127
- it('keeps the activity segment compact for narrow terminals', () => {
128
- const { lastFrame } = render(
129
- <StatusBar
130
- {...baseProps}
131
- isThinking={true}
132
- activeToolCount={12}
133
- activeBackgroundTaskCount={9}
134
- hasPendingPrompt={true}
135
- />,
136
- );
137
- const frame = lastFrame()!;
138
- const firstLine = frame.split('\n')[0] ?? '';
139
- const activityEnd = firstLine.indexOf('test-model');
140
- const activitySegment = firstLine.slice(0, activityEnd);
141
- expect(activitySegment).toContain('Tools (12)');
142
- expect(activitySegment.length).toBeLessThanOrEqual(40);
143
- });
144
-
145
- it('renders git branch when provided', () => {
146
- const { lastFrame } = render(<StatusBar {...baseProps} gitBranch="feat/status-line" />);
147
- const frame = lastFrame()!;
148
- expect(frame).toContain('feat/status-line');
149
- });
150
-
151
- it('does not render git branch when visibility is disabled', () => {
152
- const { lastFrame } = render(
153
- <StatusBar {...baseProps} gitBranch="feat/status-line" showGitBranch={false} />,
154
- );
155
- const frame = lastFrame()!;
156
- expect(frame).not.toContain('feat/status-line');
157
- });
158
-
159
- it('TC-03: shows the active preset id when set and non-default', () => {
160
- const { lastFrame } = render(<StatusBar {...baseProps} activePresetId="autonomous-builder" />);
161
- const frame = lastFrame()!;
162
- expect(frame).toContain('Preset:');
163
- expect(frame).toContain('autonomous-builder');
164
- });
165
-
166
- it('TC-03: hides the default active preset', () => {
167
- const { lastFrame } = render(<StatusBar {...baseProps} activePresetId="default" />);
168
- const frame = lastFrame()!;
169
- expect(frame).not.toContain('Preset:');
170
- });
171
-
172
- it('TC-03: hides the preset label when no active preset is provided', () => {
173
- const { lastFrame } = render(<StatusBar {...baseProps} activePresetId={undefined} />);
174
- const frame = lastFrame()!;
175
- expect(frame).not.toContain('Preset:');
176
- });
177
- });
@@ -1,137 +0,0 @@
1
- import React from 'react';
2
- import { describe, it, expect } from 'vitest';
3
- import { render } from 'ink-testing-library';
4
- import StreamingIndicator from '../StreamingIndicator.js';
5
-
6
- describe('StreamingIndicator', () => {
7
- it('renders empty when no tools and no text', () => {
8
- const { lastFrame } = render(<StreamingIndicator text="" activeTools={[]} />);
9
- expect(lastFrame()).not.toContain('Thinking');
10
- });
11
-
12
- it('shows a generic thinking state when foreground work has no text or tools yet', () => {
13
- const { lastFrame } = render(<StreamingIndicator text="" activeTools={[]} isThinking={true} />);
14
-
15
- expect(lastFrame()).toContain('Thinking...');
16
- });
17
-
18
- it('shows Tools: section with running tool', () => {
19
- const { lastFrame } = render(
20
- <StreamingIndicator
21
- text=""
22
- activeTools={[{ toolName: 'Bash', firstArg: 'ls -la', isRunning: true }]}
23
- />,
24
- );
25
- const frame = lastFrame()!;
26
- expect(frame).toContain('Tools:');
27
- expect(frame).toContain('Bash(ls -la)');
28
- });
29
-
30
- it('shows ⟳ for running and ✓ for completed tools', () => {
31
- const { lastFrame } = render(
32
- <StreamingIndicator
33
- text=""
34
- activeTools={[
35
- { toolName: 'Read', firstArg: '/src/index.ts', isRunning: false },
36
- { toolName: 'Bash', firstArg: 'ls', isRunning: true },
37
- ]}
38
- />,
39
- );
40
- const frame = lastFrame()!;
41
- expect(frame).toContain('✓ Read(/src/index.ts)');
42
- expect(frame).toContain('⟳ Bash(ls)');
43
- });
44
-
45
- it('shows Robota: section with streaming text', () => {
46
- const { lastFrame } = render(<StreamingIndicator text="Hello world" activeTools={[]} />);
47
- const frame = lastFrame()!;
48
- expect(frame).toContain('Robota:');
49
- expect(frame).toContain('Hello world');
50
- expect(frame).not.toContain('Tools:');
51
- });
52
-
53
- it('preserves CJK and emoji text in streaming output', () => {
54
- const text = '긴 한국어 응답과 emoji 🎉 를 스트리밍합니다';
55
- const { lastFrame } = render(<StreamingIndicator text={text} activeTools={[]} />);
56
-
57
- expect(lastFrame()).toContain(text);
58
- });
59
-
60
- it('shows Tools: before Robota: when both present', () => {
61
- const { lastFrame } = render(
62
- <StreamingIndicator
63
- text="Analyzing..."
64
- activeTools={[{ toolName: 'Read', firstArg: 'file.ts', isRunning: false }]}
65
- />,
66
- );
67
- const frame = lastFrame()!;
68
- const toolsIndex = frame.indexOf('Tools:');
69
- const robotaIndex = frame.indexOf('Robota:');
70
- expect(toolsIndex).toBeGreaterThanOrEqual(0);
71
- expect(robotaIndex).toBeGreaterThanOrEqual(0);
72
- expect(toolsIndex).toBeLessThan(robotaIndex);
73
- });
74
-
75
- it('does not show Thinking... when tools are active', () => {
76
- const { lastFrame } = render(
77
- <StreamingIndicator
78
- text=""
79
- activeTools={[{ toolName: 'Glob', firstArg: '**/*.md', isRunning: true }]}
80
- />,
81
- );
82
- expect(lastFrame()).not.toContain('Thinking...');
83
- });
84
-
85
- it('does not show Thinking... when text is present', () => {
86
- const { lastFrame } = render(
87
- <StreamingIndicator text="Some response" activeTools={[]} isThinking={true} />,
88
- );
89
- expect(lastFrame()).not.toContain('Thinking...');
90
- });
91
-
92
- it('shows multiple tools in order', () => {
93
- const { lastFrame } = render(
94
- <StreamingIndicator
95
- text=""
96
- activeTools={[
97
- { toolName: 'Read', firstArg: 'a.ts', isRunning: false },
98
- { toolName: 'Bash', firstArg: 'echo hi', isRunning: false },
99
- { toolName: 'Glob', firstArg: '**/*.md', isRunning: true },
100
- ]}
101
- />,
102
- );
103
- const frame = lastFrame()!;
104
- const readIdx = frame.indexOf('Read(a.ts)');
105
- const bashIdx = frame.indexOf('Bash(echo hi)');
106
- const globIdx = frame.indexOf('Glob(**/*.md)');
107
- expect(readIdx).toBeLessThan(bashIdx);
108
- expect(bashIdx).toBeLessThan(globIdx);
109
- });
110
-
111
- it('renders tool diffs through markdown diff body format', () => {
112
- const { lastFrame } = render(
113
- <StreamingIndicator
114
- text=""
115
- activeTools={[
116
- {
117
- toolName: 'Edit',
118
- firstArg: '/src/index.ts',
119
- isRunning: false,
120
- result: 'success',
121
- diffFile: '/src/index.ts',
122
- diffLines: [
123
- { type: 'remove', lineNumber: 1, text: 'const oldValue = true;' },
124
- { type: 'add', lineNumber: 1, text: 'const newValue = true;' },
125
- ],
126
- },
127
- ]}
128
- />,
129
- );
130
-
131
- const frame = lastFrame() ?? '';
132
- expect(frame).toContain('/src/index.ts');
133
- expect(frame).toContain('- 1 | const oldValue = true;');
134
- expect(frame).toContain('+ 1 | const newValue = true;');
135
- expect(frame).not.toContain('│ 1 - const oldValue = true;');
136
- });
137
- });
@@ -1,77 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import {
3
- applyTextPromptInput,
4
- createTextPromptFlowState,
5
- getTextPromptInputAction,
6
- type ITextPromptFlowState,
7
- } from '../flows/text-prompt-flow.js';
8
-
9
- describe('text prompt flow', () => {
10
- it('Given typed text When submit is applied Then it emits trimmed submit value', () => {
11
- const typed = applyTextPromptInput(
12
- createTextPromptFlowState(),
13
- { type: 'insert', value: ' hello ' },
14
- { allowEmpty: false },
15
- ).state;
16
-
17
- const result = applyTextPromptInput(typed, { type: 'submit' }, { allowEmpty: false });
18
-
19
- expect(result.effect).toEqual({ type: 'submit', value: 'hello' });
20
- expect(result.state.resolved).toBe(true);
21
- });
22
-
23
- it('Given empty required text When submit is applied Then validation error stays in state', () => {
24
- const result = applyTextPromptInput(
25
- createTextPromptFlowState(),
26
- { type: 'submit' },
27
- { allowEmpty: false, validate: (value) => (value.length === 0 ? 'Required' : undefined) },
28
- );
29
-
30
- expect(result.effect).toEqual({ type: 'none' });
31
- expect(result.state.error).toBe('Required');
32
- expect(result.state.resolved).toBe(false);
33
- });
34
-
35
- it('Given defaultable empty text When submit is applied Then empty submit is allowed', () => {
36
- const result = applyTextPromptInput(
37
- createTextPromptFlowState(),
38
- { type: 'submit' },
39
- { allowEmpty: true },
40
- );
41
-
42
- expect(result.effect).toEqual({ type: 'submit', value: '' });
43
- });
44
-
45
- it('Given text with an error When delete or insert is applied Then the error is cleared', () => {
46
- const state: ITextPromptFlowState = { value: 'ab', error: 'Invalid', resolved: false };
47
-
48
- const deleted = applyTextPromptInput(state, { type: 'delete' }, { allowEmpty: false });
49
- const inserted = applyTextPromptInput(
50
- state,
51
- { type: 'insert', value: 'c' },
52
- { allowEmpty: false },
53
- );
54
-
55
- expect(deleted.state).toMatchObject({ value: 'a', error: undefined });
56
- expect(inserted.state).toMatchObject({ value: 'abc', error: undefined });
57
- });
58
-
59
- it('Given prompt is already resolved When input is applied Then no second effect is emitted', () => {
60
- const state: ITextPromptFlowState = { value: 'done', resolved: true };
61
-
62
- const result = applyTextPromptInput(state, { type: 'submit' }, { allowEmpty: false });
63
-
64
- expect(result.effect).toEqual({ type: 'none' });
65
- expect(result.state).toBe(state);
66
- });
67
-
68
- it('Given raw Ink key info When mapped Then terminal details become prompt actions', () => {
69
- expect(getTextPromptInputAction('', { escape: true })).toEqual({ type: 'cancel' });
70
- expect(getTextPromptInputAction('', { return: true })).toEqual({ type: 'submit' });
71
- expect(getTextPromptInputAction('', { backspace: true })).toEqual({ type: 'delete' });
72
- expect(getTextPromptInputAction('x', { ctrl: false, meta: false })).toEqual({
73
- type: 'insert',
74
- value: 'x',
75
- });
76
- });
77
- });