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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +17 -14
  3. package/dist/node/headless/index.cjs +1 -1
  4. package/dist/node/headless/index.d.ts +1 -1
  5. package/dist/node/headless/index.js +1 -1
  6. package/dist/node/{headless-mRYilLfC.js → headless-DG_MEbf0.js} +7 -4
  7. package/dist/node/headless-DG_MEbf0.js.map +1 -0
  8. package/dist/node/headless-xq77JBq0.cjs +18 -0
  9. package/dist/node/{index-E8Gx4-lc.d.ts → index-CrJ2Lhn3.d.ts} +16 -3
  10. package/dist/node/index-CrJ2Lhn3.d.ts.map +1 -0
  11. package/dist/node/index-JIvzrtaA.d.ts +52 -0
  12. package/dist/node/index-JIvzrtaA.d.ts.map +1 -0
  13. package/dist/node/{index-CYl7ksS6.d.ts → index-JTD_xtEQ.d.ts} +15 -2
  14. package/dist/node/index-JTD_xtEQ.d.ts.map +1 -0
  15. package/dist/node/index-y_HBOfzO.d.ts +52 -0
  16. package/dist/node/index-y_HBOfzO.d.ts.map +1 -0
  17. package/dist/node/index.cjs +1 -1
  18. package/dist/node/index.d.ts +10 -9
  19. package/dist/node/index.d.ts.map +1 -1
  20. package/dist/node/index.js +1 -1
  21. package/dist/node/index.js.map +1 -1
  22. package/dist/node/programmatic/index.cjs +1 -0
  23. package/dist/node/programmatic/index.d.ts +2 -0
  24. package/dist/node/programmatic/index.js +1 -0
  25. package/dist/node/programmatic-6wLf68qJ.cjs +1 -0
  26. package/dist/node/programmatic-70dxIXtS.js +2 -0
  27. package/dist/node/programmatic-70dxIXtS.js.map +1 -0
  28. package/dist/node/testing/index.cjs +1 -1
  29. package/dist/node/testing/index.d.ts +2 -21
  30. package/dist/node/testing/index.js +1 -2
  31. package/package.json +15 -71
  32. package/src/__tests__/programmatic/programmatic-ask-user.test.ts +32 -0
  33. package/src/__tests__/programmatic/programmatic-driver.test.ts +107 -0
  34. package/src/__tests__/programmatic/programmatic-flows.test.ts +140 -0
  35. package/src/__tests__/transport-registry.stop-all.test.ts +53 -0
  36. package/src/headless/HeadlessInteractionChannel.ts +22 -5
  37. package/src/headless/__tests__/headless-goal-runner.test.ts +108 -0
  38. package/src/headless/__tests__/headless-runner.test.ts +2 -1
  39. package/src/headless/headless-runner.ts +67 -1
  40. package/src/headless/headless-stream-json.ts +1 -1
  41. package/src/headless/print-terminal.ts +1 -0
  42. package/src/index.ts +2 -5
  43. package/src/programmatic/ProgrammaticInteractionChannel.ts +82 -0
  44. package/src/programmatic/createProgrammaticAgent.ts +71 -0
  45. package/src/programmatic/index.ts +5 -0
  46. package/src/testing/index.ts +7 -4
  47. package/src/transport-registry.ts +15 -12
  48. package/dist/node/headless-CT2ibQnr.cjs +0 -15
  49. package/dist/node/headless-mRYilLfC.js.map +0 -1
  50. package/dist/node/http/index.cjs +0 -1
  51. package/dist/node/http/index.d.ts +0 -2
  52. package/dist/node/http/index.js +0 -1
  53. package/dist/node/http-2Jiuflc1.js +0 -2
  54. package/dist/node/http-2Jiuflc1.js.map +0 -1
  55. package/dist/node/http-CBAvefLw.cjs +0 -1
  56. package/dist/node/index-BNccqSpv.d.ts +0 -86
  57. package/dist/node/index-BNccqSpv.d.ts.map +0 -1
  58. package/dist/node/index-BUhHIf7X.d.ts +0 -86
  59. package/dist/node/index-BUhHIf7X.d.ts.map +0 -1
  60. package/dist/node/index-BnAGE-u9.d.ts +0 -33
  61. package/dist/node/index-BnAGE-u9.d.ts.map +0 -1
  62. package/dist/node/index-BrQ4gGw0.d.ts +0 -213
  63. package/dist/node/index-BrQ4gGw0.d.ts.map +0 -1
  64. package/dist/node/index-CYl7ksS6.d.ts.map +0 -1
  65. package/dist/node/index-CoeBF21y.d.ts +0 -213
  66. package/dist/node/index-CoeBF21y.d.ts.map +0 -1
  67. package/dist/node/index-DHt-2VQ-.d.ts +0 -46
  68. package/dist/node/index-DHt-2VQ-.d.ts.map +0 -1
  69. package/dist/node/index-DMwKN5Le.d.ts +0 -33
  70. package/dist/node/index-DMwKN5Le.d.ts.map +0 -1
  71. package/dist/node/index-E8Gx4-lc.d.ts.map +0 -1
  72. package/dist/node/index-c0M42fsA.d.ts +0 -46
  73. package/dist/node/index-c0M42fsA.d.ts.map +0 -1
  74. package/dist/node/mcp/index.cjs +0 -1
  75. package/dist/node/mcp/index.d.ts +0 -2
  76. package/dist/node/mcp/index.js +0 -1
  77. package/dist/node/mcp-BOglBJNy.cjs +0 -1
  78. package/dist/node/mcp-D3BBVK7C.js +0 -2
  79. package/dist/node/mcp-D3BBVK7C.js.map +0 -1
  80. package/dist/node/rolldown-runtime-CMqjfN_6.cjs +0 -1
  81. package/dist/node/testing/index.d.ts.map +0 -1
  82. package/dist/node/testing/index.js.map +0 -1
  83. package/dist/node/tui/index.cjs +0 -1
  84. package/dist/node/tui/index.d.ts +0 -2
  85. package/dist/node/tui/index.js +0 -1
  86. package/dist/node/tui-CcH5EsQh.js +0 -25
  87. package/dist/node/tui-CcH5EsQh.js.map +0 -1
  88. package/dist/node/tui-DznRbcku.cjs +0 -24
  89. package/dist/node/ws/index.cjs +0 -1
  90. package/dist/node/ws/index.d.ts +0 -2
  91. package/dist/node/ws/index.js +0 -1
  92. package/dist/node/ws-Dc2RUwVs.js +0 -2
  93. package/dist/node/ws-Dc2RUwVs.js.map +0 -1
  94. package/dist/node/ws-QNMQn5kg.cjs +0 -1
  95. package/src/http/__tests__/http-transport.test.ts +0 -55
  96. package/src/http/__tests__/routes.test.ts +0 -168
  97. package/src/http/http-transport.ts +0 -41
  98. package/src/http/index.ts +0 -4
  99. package/src/http/routes.ts +0 -152
  100. package/src/mcp/__tests__/mcp-server.test.ts +0 -66
  101. package/src/mcp/__tests__/mcp-transport.test.ts +0 -46
  102. package/src/mcp/index.ts +0 -4
  103. package/src/mcp/mcp-server.ts +0 -163
  104. package/src/mcp/mcp-transport.ts +0 -48
  105. package/src/testing/__tests__/scripted-provider.test.ts +0 -73
  106. package/src/testing/scripted-provider.ts +0 -73
  107. package/src/tui/App.tsx +0 -491
  108. package/src/tui/BackgroundTaskPanel.tsx +0 -36
  109. package/src/tui/CjkTextInput.tsx +0 -199
  110. package/src/tui/ConfirmPrompt.tsx +0 -70
  111. package/src/tui/ContextWarningBanner.tsx +0 -34
  112. package/src/tui/ExecutionWorkspaceDetailPane.tsx +0 -64
  113. package/src/tui/ExecutionWorkspaceSwitcher.tsx +0 -187
  114. package/src/tui/InputArea.tsx +0 -310
  115. package/src/tui/InteractivePrompt.tsx +0 -59
  116. package/src/tui/ListPicker.tsx +0 -95
  117. package/src/tui/MenuSelect.tsx +0 -104
  118. package/src/tui/MessageList.tsx +0 -284
  119. package/src/tui/PermissionPrompt.tsx +0 -86
  120. package/src/tui/PluginTUI.tsx +0 -258
  121. package/src/tui/SessionPicker.tsx +0 -68
  122. package/src/tui/SessionStatusBar.tsx +0 -73
  123. package/src/tui/SlashAutocomplete.tsx +0 -110
  124. package/src/tui/StatusBar.tsx +0 -236
  125. package/src/tui/StreamingIndicator.tsx +0 -93
  126. package/src/tui/TextPrompt.tsx +0 -81
  127. package/src/tui/ToolCommandOutput.tsx +0 -39
  128. package/src/tui/ToolDiffBlock.tsx +0 -32
  129. package/src/tui/TransportTUI.tsx +0 -117
  130. package/src/tui/TuiInteractionChannel.ts +0 -495
  131. package/src/tui/UpdateNotice.tsx +0 -14
  132. package/src/tui/UsageSummaryEntry.tsx +0 -39
  133. package/src/tui/WaveText.tsx +0 -44
  134. package/src/tui/__tests__/InteractivePrompt.test.tsx +0 -82
  135. package/src/tui/__tests__/ListPicker.test.tsx +0 -159
  136. package/src/tui/__tests__/MenuSelect.test.tsx +0 -103
  137. package/src/tui/__tests__/PluginTUI.test.tsx +0 -167
  138. package/src/tui/__tests__/SlashAutocomplete.test.tsx +0 -140
  139. package/src/tui/__tests__/TextPrompt.test.tsx +0 -98
  140. package/src/tui/__tests__/TuiInteractionChannel.display-contract.test.ts +0 -239
  141. package/src/tui/__tests__/TuiInteractionChannel.lifecycle.test.ts +0 -297
  142. package/src/tui/__tests__/TuiInteractionChannel.requestAction.test.ts +0 -124
  143. package/src/tui/__tests__/UpdateNotice.test.tsx +0 -15
  144. package/src/tui/__tests__/abort-after-permission.test.tsx +0 -169
  145. package/src/tui/__tests__/abort-streaming-e2e.test.tsx +0 -183
  146. package/src/tui/__tests__/background-task-panel.test.tsx +0 -53
  147. package/src/tui/__tests__/background-task-row-format.test.ts +0 -59
  148. package/src/tui/__tests__/channel-factory-integration.test.ts +0 -138
  149. package/src/tui/__tests__/cjk-text-input-flow.test.ts +0 -109
  150. package/src/tui/__tests__/cjk-text-input.test.ts +0 -191
  151. package/src/tui/__tests__/command-effect-handler.test.ts +0 -127
  152. package/src/tui/__tests__/command-output-summary.test.ts +0 -95
  153. package/src/tui/__tests__/compact-event-bridge.test.ts +0 -20
  154. package/src/tui/__tests__/confirm-permission-flow.test.ts +0 -130
  155. package/src/tui/__tests__/confirm-prompt.test.tsx +0 -87
  156. package/src/tui/__tests__/execution-workspace-switcher.test.tsx +0 -110
  157. package/src/tui/__tests__/execution-workspace-view-model.test.ts +0 -93
  158. package/src/tui/__tests__/fixtures/provider-setup-prompt-driver.tsx +0 -125
  159. package/src/tui/__tests__/input-area-flow.test.ts +0 -164
  160. package/src/tui/__tests__/message-list-rendering.test.tsx +0 -353
  161. package/src/tui/__tests__/prompt-queue.test.tsx +0 -255
  162. package/src/tui/__tests__/provider-setup-pty-e2e.test.ts +0 -233
  163. package/src/tui/__tests__/pty/pty-driver.ts +0 -135
  164. package/src/tui/__tests__/pty/tui-pty.ptytest.ts +0 -61
  165. package/src/tui/__tests__/render-channel-options.test.ts +0 -32
  166. package/src/tui/__tests__/render-markdown.test.ts +0 -72
  167. package/src/tui/__tests__/selection-flow.test.ts +0 -61
  168. package/src/tui/__tests__/session-init-poller.test.ts +0 -102
  169. package/src/tui/__tests__/session-naming.test.ts +0 -64
  170. package/src/tui/__tests__/session-switch-channel.test.tsx +0 -307
  171. package/src/tui/__tests__/slash-routing-effects.test.ts +0 -228
  172. package/src/tui/__tests__/status-activity.test.ts +0 -71
  173. package/src/tui/__tests__/status-bar.test.tsx +0 -177
  174. package/src/tui/__tests__/streaming-indicator.test.tsx +0 -137
  175. package/src/tui/__tests__/text-prompt-flow.test.ts +0 -77
  176. package/src/tui/__tests__/tui-channel-init-failure.test.ts +0 -57
  177. package/src/tui/__tests__/tui-state-manager.test.ts +0 -401
  178. package/src/tui/background-task-row-format.ts +0 -53
  179. package/src/tui/command-interaction.ts +0 -9
  180. package/src/tui/command-output-summary.ts +0 -122
  181. package/src/tui/create-default-tui-cli-adapter.ts +0 -41
  182. package/src/tui/execution-workspace-view-model.ts +0 -123
  183. package/src/tui/flows/cjk-text-input-flow.ts +0 -285
  184. package/src/tui/flows/confirm-prompt-flow.ts +0 -45
  185. package/src/tui/flows/input-area-flow.ts +0 -189
  186. package/src/tui/flows/permission-prompt-flow.ts +0 -85
  187. package/src/tui/flows/selection-flow.ts +0 -126
  188. package/src/tui/flows/session-init-poller.ts +0 -77
  189. package/src/tui/flows/text-prompt-flow.ts +0 -98
  190. package/src/tui/hooks/command-effect-handler.ts +0 -97
  191. package/src/tui/hooks/command-effect-queue.ts +0 -39
  192. package/src/tui/hooks/side-effects-types.ts +0 -35
  193. package/src/tui/hooks/useAutocomplete.ts +0 -87
  194. package/src/tui/hooks/usePluginCallbacks.ts +0 -31
  195. package/src/tui/hooks/usePluginScreenData.ts +0 -85
  196. package/src/tui/hooks/useSideEffects.ts +0 -175
  197. package/src/tui/hooks/useSlashRouting.ts +0 -118
  198. package/src/tui/hooks/useStatusLineSettings.ts +0 -37
  199. package/src/tui/hooks/useTuiChannel.ts +0 -95
  200. package/src/tui/index.ts +0 -14
  201. package/src/tui/interactions/CommandConfirm.tsx +0 -36
  202. package/src/tui/interactions/CommandPicker.tsx +0 -77
  203. package/src/tui/interactions/__tests__/CommandConfirm.test.tsx +0 -124
  204. package/src/tui/interactions/__tests__/CommandPicker.test.tsx +0 -138
  205. package/src/tui/plugin-tui-handlers.ts +0 -163
  206. package/src/tui/render-markdown.ts +0 -130
  207. package/src/tui/render.tsx +0 -129
  208. package/src/tui/session-naming.ts +0 -33
  209. package/src/tui/status-activity.ts +0 -63
  210. package/src/tui/tui-cli-adapter-context.tsx +0 -13
  211. package/src/tui/tui-cli-adapter.ts +0 -25
  212. package/src/tui/tui-state-manager.ts +0 -226
  213. package/src/tui/tui-transport.ts +0 -35
  214. package/src/tui/types.ts +0 -15
  215. package/src/tui/utils/__tests__/edit-diff.test.ts +0 -426
  216. package/src/tui/utils/__tests__/paste-detection.test.ts +0 -116
  217. package/src/tui/utils/__tests__/paste-labels.test.ts +0 -46
  218. package/src/tui/utils/__tests__/tool-call-extractor.test.ts +0 -227
  219. package/src/tui/utils/__tests__/tool-diff-summary.test.ts +0 -104
  220. package/src/tui/utils/edit-diff.ts +0 -153
  221. package/src/tui/utils/paste-labels.ts +0 -9
  222. package/src/tui/utils/tool-call-extractor.ts +0 -92
  223. package/src/tui/utils/tool-diff-summary.ts +0 -75
  224. package/src/ws/__tests__/ws-handler.test.ts +0 -409
  225. package/src/ws/__tests__/ws-transport.test.ts +0 -53
  226. package/src/ws/index.ts +0 -13
  227. package/src/ws/ws-background-messages.ts +0 -170
  228. package/src/ws/ws-handler.ts +0 -280
  229. package/src/ws/ws-protocol.ts +0 -78
  230. package/src/ws/ws-transport-configurable.ts +0 -128
  231. package/src/ws/ws-transport.ts +0 -42
@@ -1,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
- });