@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,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,158 +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
- });
@@ -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
- });