@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,426 +0,0 @@
1
- import { describe, it, expect, afterEach } from 'vitest';
2
- import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
3
- import { join } from 'node:path';
4
- import { tmpdir } from 'node:os';
5
- import { generateDiffLines, generateDiffLinesWithContext, extractEditDiff } from '../edit-diff.js';
6
-
7
- describe('generateDiffLines', () => {
8
- it('single line change: 1 remove + 1 add', () => {
9
- const lines = generateDiffLines('hello', 'world');
10
- expect(lines).toEqual([
11
- { type: 'remove', text: 'hello', lineNumber: 1 },
12
- { type: 'add', text: 'world', lineNumber: 1 },
13
- ]);
14
- });
15
-
16
- it('multi-line change: each old line is remove, each new line is add', () => {
17
- const lines = generateDiffLines('line1\nline2\nline3', 'lineA\nlineB');
18
- expect(lines.filter((l) => l.type === 'remove')).toHaveLength(3);
19
- expect(lines.filter((l) => l.type === 'add')).toHaveLength(2);
20
- expect(lines[0]).toEqual({ type: 'remove', text: 'line1', lineNumber: 1 });
21
- expect(lines[1]).toEqual({ type: 'remove', text: 'line2', lineNumber: 2 });
22
- expect(lines[2]).toEqual({ type: 'remove', text: 'line3', lineNumber: 3 });
23
- expect(lines[3]).toEqual({ type: 'add', text: 'lineA', lineNumber: 1 });
24
- expect(lines[4]).toEqual({ type: 'add', text: 'lineB', lineNumber: 2 });
25
- });
26
-
27
- it('identical strings return empty array', () => {
28
- expect(generateDiffLines('same', 'same')).toEqual([]);
29
- });
30
-
31
- it('empty old string (new content) returns only add lines', () => {
32
- const lines = generateDiffLines('', 'new line');
33
- expect(lines.filter((l) => l.type === 'remove')).toHaveLength(1); // '' splits to ['']
34
- expect(lines.filter((l) => l.type === 'add')).toHaveLength(1);
35
- });
36
-
37
- it('empty new string (deletion) returns only remove lines', () => {
38
- const lines = generateDiffLines('old line', '');
39
- expect(lines.filter((l) => l.type === 'remove')).toHaveLength(1);
40
- expect(lines.filter((l) => l.type === 'add')).toHaveLength(1); // '' splits to ['']
41
- });
42
-
43
- it('marks ALL old lines as remove and ALL new lines as add (no smart diff)', () => {
44
- // Even when some lines are the same, the function does not detect unchanged lines
45
- const lines = generateDiffLines('shared\nold', 'shared\nnew');
46
- expect(lines).toEqual([
47
- { type: 'remove', text: 'shared', lineNumber: 1 },
48
- { type: 'remove', text: 'old', lineNumber: 2 },
49
- { type: 'add', text: 'shared', lineNumber: 1 },
50
- { type: 'add', text: 'new', lineNumber: 2 },
51
- ]);
52
- });
53
-
54
- it('default startLine is 1', () => {
55
- const lines = generateDiffLines('a', 'b');
56
- expect(lines[0]).toEqual({ type: 'remove', text: 'a', lineNumber: 1 });
57
- expect(lines[1]).toEqual({ type: 'add', text: 'b', lineNumber: 1 });
58
- });
59
-
60
- it('custom startLine produces correct line numbers', () => {
61
- const lines = generateDiffLines('a', 'b', 42);
62
- expect(lines[0]).toEqual({ type: 'remove', text: 'a', lineNumber: 42 });
63
- expect(lines[1]).toEqual({ type: 'add', text: 'b', lineNumber: 42 });
64
- });
65
-
66
- it('multi-line with startLine: remove lines get startLine, startLine+1, etc.', () => {
67
- const lines = generateDiffLines('line1\nline2\nline3', 'lineA\nlineB', 10);
68
- expect(lines[0]).toEqual({ type: 'remove', text: 'line1', lineNumber: 10 });
69
- expect(lines[1]).toEqual({ type: 'remove', text: 'line2', lineNumber: 11 });
70
- expect(lines[2]).toEqual({ type: 'remove', text: 'line3', lineNumber: 12 });
71
- expect(lines[3]).toEqual({ type: 'add', text: 'lineA', lineNumber: 10 });
72
- expect(lines[4]).toEqual({ type: 'add', text: 'lineB', lineNumber: 11 });
73
- });
74
- });
75
-
76
- describe('extractEditDiff', () => {
77
- it('Edit tool with valid args returns file and lines', () => {
78
- const result = extractEditDiff('Edit', {
79
- file_path: '/src/index.ts',
80
- old_string: 'hello',
81
- new_string: 'world',
82
- });
83
- expect(result).not.toBeNull();
84
- expect(result!.file).toBe('/src/index.ts');
85
- expect(result!.lines).toEqual([
86
- { type: 'remove', text: 'hello', lineNumber: 1 },
87
- { type: 'add', text: 'world', lineNumber: 1 },
88
- ]);
89
- });
90
-
91
- it('non-Edit tool returns null', () => {
92
- expect(
93
- extractEditDiff('Read', {
94
- file_path: '/src/index.ts',
95
- old_string: 'a',
96
- new_string: 'b',
97
- }),
98
- ).toBeNull();
99
- });
100
-
101
- it('missing toolArgs returns null', () => {
102
- expect(extractEditDiff('Edit')).toBeNull();
103
- expect(extractEditDiff('Edit', undefined)).toBeNull();
104
- });
105
-
106
- it('missing file_path returns null', () => {
107
- expect(
108
- extractEditDiff('Edit', {
109
- old_string: 'a',
110
- new_string: 'b',
111
- }),
112
- ).toBeNull();
113
- });
114
-
115
- it('missing old_string returns null', () => {
116
- expect(
117
- extractEditDiff('Edit', {
118
- file_path: '/src/index.ts',
119
- new_string: 'b',
120
- }),
121
- ).toBeNull();
122
- });
123
-
124
- it('missing new_string returns null', () => {
125
- expect(
126
- extractEditDiff('Edit', {
127
- file_path: '/src/index.ts',
128
- old_string: 'a',
129
- }),
130
- ).toBeNull();
131
- });
132
-
133
- it('identical old_string and new_string returns null', () => {
134
- expect(
135
- extractEditDiff('Edit', {
136
- file_path: '/src/index.ts',
137
- old_string: 'same',
138
- new_string: 'same',
139
- }),
140
- ).toBeNull();
141
- });
142
-
143
- it('handles camelCase field names (filePath, oldString, newString)', () => {
144
- const result = extractEditDiff('Edit', {
145
- filePath: '/src/index.ts',
146
- oldString: 'old',
147
- newString: 'new',
148
- });
149
- expect(result).not.toBeNull();
150
- expect(result!.file).toBe('/src/index.ts');
151
- expect(result!.lines).toHaveLength(2);
152
- });
153
-
154
- it('handles snake_case field names (file_path, old_string, new_string)', () => {
155
- const result = extractEditDiff('Edit', {
156
- file_path: '/src/main.ts',
157
- old_string: 'foo',
158
- new_string: 'bar',
159
- });
160
- expect(result).not.toBeNull();
161
- expect(result!.file).toBe('/src/main.ts');
162
- });
163
-
164
- it('prefers snake_case over camelCase when both present', () => {
165
- const result = extractEditDiff('Edit', {
166
- file_path: '/snake.ts',
167
- filePath: '/camel.ts',
168
- old_string: 'a',
169
- oldString: 'x',
170
- new_string: 'b',
171
- newString: 'y',
172
- });
173
- expect(result).not.toBeNull();
174
- expect(result!.file).toBe('/snake.ts');
175
- });
176
-
177
- it('accepts optional startLine parameter', () => {
178
- const result = extractEditDiff(
179
- 'Edit',
180
- {
181
- file_path: '/src/index.ts',
182
- old_string: 'hello',
183
- new_string: 'world',
184
- },
185
- 5,
186
- );
187
- expect(result).not.toBeNull();
188
- expect(result!.lines).toEqual([
189
- { type: 'remove', text: 'hello', lineNumber: 5 },
190
- { type: 'add', text: 'world', lineNumber: 5 },
191
- ]);
192
- });
193
-
194
- it('defaults startLine to 1 when not provided', () => {
195
- const result = extractEditDiff('Edit', {
196
- file_path: '/src/index.ts',
197
- old_string: 'a',
198
- new_string: 'b',
199
- });
200
- expect(result).not.toBeNull();
201
- expect(result!.lines[0].lineNumber).toBe(1);
202
- expect(result!.lines[1].lineNumber).toBe(1);
203
- });
204
- });
205
-
206
- // ─── Regression tests: context lines, absolute line numbers, startLine resolution ───
207
-
208
- describe('generateDiffLinesWithContext', () => {
209
- let tmpDir: string;
210
-
211
- afterEach(() => {
212
- if (tmpDir) {
213
- rmSync(tmpDir, { recursive: true, force: true });
214
- }
215
- });
216
-
217
- function makeTempFile(content: string): string {
218
- tmpDir = mkdtempSync(join(tmpdir(), 'edit-diff-test-'));
219
- const filePath = join(tmpDir, 'test.ts');
220
- writeFileSync(filePath, content, 'utf-8');
221
- return filePath;
222
- }
223
-
224
- it('should include 3 context lines before the change', () => {
225
- // 10-line file, edit line 5 (replaced already in file)
226
- const fileLines = Array.from({ length: 10 }, (_, i) => `line${i + 1}`);
227
- // Simulate: line5 was replaced with lineNEW
228
- const modifiedLines = [...fileLines];
229
- modifiedLines[4] = 'lineNEW';
230
- const filePath = makeTempFile(modifiedLines.join('\n'));
231
-
232
- const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
233
-
234
- // Context before: line2, line3, line4
235
- const contextBefore = result.filter((l) => l.type === 'context' && l.lineNumber < 5);
236
- expect(contextBefore).toHaveLength(3);
237
- expect(contextBefore[0]).toEqual({ type: 'context', text: 'line2', lineNumber: 2 });
238
- expect(contextBefore[1]).toEqual({ type: 'context', text: 'line3', lineNumber: 3 });
239
- expect(contextBefore[2]).toEqual({ type: 'context', text: 'line4', lineNumber: 4 });
240
- });
241
-
242
- it('should include 3 context lines after the change', () => {
243
- const fileLines = Array.from({ length: 10 }, (_, i) => `line${i + 1}`);
244
- const modifiedLines = [...fileLines];
245
- modifiedLines[4] = 'lineNEW';
246
- const filePath = makeTempFile(modifiedLines.join('\n'));
247
-
248
- const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
249
-
250
- // Context after: line6, line7, line8
251
- const contextAfter = result.filter((l) => l.type === 'context' && l.lineNumber > 5);
252
- expect(contextAfter).toHaveLength(3);
253
- expect(contextAfter[0]).toEqual({ type: 'context', text: 'line6', lineNumber: 6 });
254
- expect(contextAfter[1]).toEqual({ type: 'context', text: 'line7', lineNumber: 7 });
255
- expect(contextAfter[2]).toEqual({ type: 'context', text: 'line8', lineNumber: 8 });
256
- });
257
-
258
- it('should handle edit at start of file (no lines before)', () => {
259
- const fileLines = Array.from({ length: 5 }, (_, i) => `line${i + 1}`);
260
- const modifiedLines = [...fileLines];
261
- modifiedLines[0] = 'lineNEW';
262
- const filePath = makeTempFile(modifiedLines.join('\n'));
263
-
264
- const result = generateDiffLinesWithContext('line1', 'lineNEW', 1, filePath);
265
-
266
- const contextBefore = result.filter((l) => l.type === 'context' && l.lineNumber < 1);
267
- expect(contextBefore).toHaveLength(0);
268
-
269
- // Context after: line2, line3, line4
270
- const contextAfter = result.filter((l) => l.type === 'context' && l.lineNumber > 1);
271
- expect(contextAfter).toHaveLength(3);
272
- expect(contextAfter[0]).toEqual({ type: 'context', text: 'line2', lineNumber: 2 });
273
- expect(contextAfter[1]).toEqual({ type: 'context', text: 'line3', lineNumber: 3 });
274
- expect(contextAfter[2]).toEqual({ type: 'context', text: 'line4', lineNumber: 4 });
275
- });
276
-
277
- it('should handle edit at end of file (no lines after)', () => {
278
- const fileLines = Array.from({ length: 5 }, (_, i) => `line${i + 1}`);
279
- const modifiedLines = [...fileLines];
280
- modifiedLines[4] = 'lineNEW';
281
- const filePath = makeTempFile(modifiedLines.join('\n'));
282
-
283
- const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
284
-
285
- // Context before: line2, line3, line4
286
- const contextBefore = result.filter((l) => l.type === 'context' && l.lineNumber < 5);
287
- expect(contextBefore).toHaveLength(3);
288
-
289
- // Context after: none (line5 is the last line)
290
- const contextAfter = result.filter((l) => l.type === 'context' && l.lineNumber > 5);
291
- expect(contextAfter).toHaveLength(0);
292
- });
293
-
294
- it('context lines should have type "context"', () => {
295
- const fileLines = Array.from({ length: 10 }, (_, i) => `line${i + 1}`);
296
- const modifiedLines = [...fileLines];
297
- modifiedLines[4] = 'lineNEW';
298
- const filePath = makeTempFile(modifiedLines.join('\n'));
299
-
300
- const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
301
-
302
- const contextLines = result.filter((l) => l.type === 'context');
303
- expect(contextLines.length).toBeGreaterThan(0);
304
- for (const line of contextLines) {
305
- expect(line.type).toBe('context');
306
- }
307
- });
308
-
309
- it('starts readable context diffs with a hunk header', () => {
310
- const fileLines = Array.from({ length: 10 }, (_, i) => `line${i + 1}`);
311
- const modifiedLines = [...fileLines];
312
- modifiedLines[4] = 'lineNEW';
313
- const filePath = makeTempFile(modifiedLines.join('\n'));
314
-
315
- const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
316
-
317
- expect(result[0]).toEqual({
318
- type: 'hunk',
319
- text: '@@ -2,7 +2,7 @@',
320
- lineNumber: 2,
321
- });
322
- });
323
-
324
- it('all lines should have absolute lineNumber', () => {
325
- const fileLines = Array.from({ length: 10 }, (_, i) => `line${i + 1}`);
326
- const modifiedLines = [...fileLines];
327
- modifiedLines[4] = 'lineNEW';
328
- const filePath = makeTempFile(modifiedLines.join('\n'));
329
-
330
- const result = generateDiffLinesWithContext('line5', 'lineNEW', 5, filePath);
331
-
332
- for (const line of result) {
333
- expect(typeof line.lineNumber).toBe('number');
334
- expect(line.lineNumber).toBeGreaterThan(0);
335
- }
336
- });
337
-
338
- it('returns diff without context when file is not readable', () => {
339
- const result = generateDiffLinesWithContext('old', 'new', 5, '/nonexistent/path/file.ts');
340
-
341
- // Should still return diff lines, just no context
342
- expect(result.length).toBeGreaterThan(0);
343
- const contextLines = result.filter((l) => l.type === 'context');
344
- expect(contextLines).toHaveLength(0);
345
- });
346
-
347
- it('identical strings return empty array', () => {
348
- const filePath = makeTempFile('some content');
349
- const result = generateDiffLinesWithContext('same', 'same', 1, filePath);
350
- expect(result).toEqual([]);
351
- });
352
- });
353
-
354
- describe('extractEditDiff startLine resolution', () => {
355
- let tmpDir: string;
356
-
357
- afterEach(() => {
358
- if (tmpDir) {
359
- rmSync(tmpDir, { recursive: true, force: true });
360
- }
361
- });
362
-
363
- function makeTempFile(content: string): string {
364
- tmpDir = mkdtempSync(join(tmpdir(), 'edit-diff-test-'));
365
- const filePath = join(tmpDir, 'test.ts');
366
- writeFileSync(filePath, content, 'utf-8');
367
- return filePath;
368
- }
369
-
370
- it('should use provided startLine', () => {
371
- const result = extractEditDiff(
372
- 'Edit',
373
- {
374
- file_path: '/nonexistent/file.ts',
375
- old_string: 'hello',
376
- new_string: 'world',
377
- },
378
- 42,
379
- );
380
- expect(result).not.toBeNull();
381
- const removeLines = result!.lines.filter((l) => l.type === 'remove');
382
- expect(removeLines[0].lineNumber).toBe(42);
383
- });
384
-
385
- it('should resolve startLine from file when not provided', () => {
386
- // Create file where newStr appears at line 4
387
- const content = 'line1\nline2\nline3\nREPLACED\nline5';
388
- const filePath = makeTempFile(content);
389
-
390
- const result = extractEditDiff('Edit', {
391
- file_path: filePath,
392
- old_string: 'original',
393
- new_string: 'REPLACED',
394
- });
395
-
396
- expect(result).not.toBeNull();
397
- // newStr "REPLACED" is at line 4 in the file
398
- const removeLines = result!.lines.filter((l) => l.type === 'remove');
399
- expect(removeLines[0].lineNumber).toBe(4);
400
- const addLines = result!.lines.filter((l) => l.type === 'add');
401
- expect(addLines[0].lineNumber).toBe(4);
402
- });
403
-
404
- it('should fall back to line 1 when file not readable', () => {
405
- const result = extractEditDiff('Edit', {
406
- file_path: '/nonexistent/path/file.ts',
407
- old_string: 'a',
408
- new_string: 'b',
409
- });
410
- expect(result).not.toBeNull();
411
- expect(result!.lines[0].lineNumber).toBe(1);
412
- });
413
-
414
- it('should fall back to line 1 when newString not found in file', () => {
415
- const content = 'line1\nline2\nline3';
416
- const filePath = makeTempFile(content);
417
-
418
- const result = extractEditDiff('Edit', {
419
- file_path: filePath,
420
- old_string: 'something',
421
- new_string: 'NOT_IN_FILE_ANYWHERE',
422
- });
423
- expect(result).not.toBeNull();
424
- expect(result!.lines[0].lineNumber).toBe(1);
425
- });
426
- });
@@ -1,116 +0,0 @@
1
- /**
2
- * Tests for paste detection and the full paste → label → expand pipeline.
3
- */
4
-
5
- import { describe, it, expect } from 'vitest';
6
- import { filterPrintable } from '../../flows/cjk-text-input-flow.js';
7
- import { expandPasteLabels } from '../paste-labels.js';
8
-
9
- describe('filterPrintable and newlines', () => {
10
- it('should strip newlines from input (they are control chars)', () => {
11
- expect(filterPrintable('line1\nline2')).toBe('line1line2');
12
- });
13
-
14
- it('should strip carriage returns', () => {
15
- expect(filterPrintable('line1\r\nline2')).toBe('line1line2');
16
- });
17
-
18
- it('should strip tabs', () => {
19
- expect(filterPrintable('a\tb')).toBe('ab');
20
- });
21
-
22
- it('should preserve normal text', () => {
23
- expect(filterPrintable('hello world')).toBe('hello world');
24
- });
25
- });
26
-
27
- describe('Full paste pipeline: detect → label → expand', () => {
28
- it('should correctly round-trip multiline paste content', () => {
29
- const pastedText = 'const a = 1;\nconst b = 2;\nconst c = 3;';
30
- const store = new Map<number, string>();
31
-
32
- // Simulate paste detection
33
- expect(pastedText.length > 1 && pastedText.includes('\n')).toBe(true);
34
-
35
- // Simulate store + label creation (as InputArea.handlePaste does)
36
- const id = 1;
37
- store.set(id, pastedText);
38
- const lineCount = pastedText.split('\n').length;
39
- const label = `[Pasted text #${id} +${lineCount} lines]`;
40
- expect(label).toBe('[Pasted text #1 +3 lines]');
41
-
42
- // Simulate submit → expand
43
- const expanded = expandPasteLabels(label, store);
44
- expect(expanded).toBe(pastedText);
45
- });
46
-
47
- it('should handle paste mixed with typed text', () => {
48
- const pastedText = 'function hello() {\n return "world";\n}';
49
- const store = new Map<number, string>();
50
- store.set(1, pastedText);
51
-
52
- const userInput = 'Review this code: [Pasted text #1 +3 lines] and fix any bugs';
53
- const expanded = expandPasteLabels(userInput, store);
54
- expect(expanded).toBe(
55
- 'Review this code: function hello() {\n return "world";\n} and fix any bugs',
56
- );
57
- });
58
-
59
- it('should handle multiple pastes in one message', () => {
60
- const store = new Map<number, string>();
61
- store.set(1, 'first\npaste');
62
- store.set(2, 'second\npaste\nhere');
63
-
64
- const input = '[Pasted text #1 +2 lines] compare with [Pasted text #2 +3 lines]';
65
- const expanded = expandPasteLabels(input, store);
66
- expect(expanded).toBe('first\npaste compare with second\npaste\nhere');
67
- });
68
- });
69
-
70
- describe('Paste at cursor position', () => {
71
- it('label is inserted at cursor position, not end', () => {
72
- const existingText = 'hello world';
73
- const cursorPosition = 5; // between "hello" and " world"
74
- const pastedText = 'line1\nline2';
75
- const store = new Map<number, string>();
76
- const id = 1;
77
- store.set(id, pastedText);
78
- const lineCount = pastedText.split('\n').length;
79
- const label = `[Pasted text #${id} +${lineCount} lines]`;
80
-
81
- // Simulate cursor-aware insertion (as handlePaste does)
82
- const result =
83
- existingText.slice(0, cursorPosition) + label + existingText.slice(cursorPosition);
84
- expect(result).toBe('hello[Pasted text #1 +2 lines] world');
85
-
86
- // Expand should restore original text at correct position
87
- const expanded = expandPasteLabels(result, store);
88
- expect(expanded).toBe('helloline1\nline2 world');
89
- });
90
-
91
- it('cursor hint equals cursorPosition + label.length', () => {
92
- const cursorPosition = 5;
93
- const label = '[Pasted text #1 +3 lines]';
94
- const newCursorPos = cursorPosition + label.length;
95
- expect(newCursorPos).toBe(5 + 25); // label is 25 chars
96
- expect(newCursorPos).toBe(30);
97
- });
98
-
99
- it('paste at start (cursor = 0)', () => {
100
- const existingText = 'existing';
101
- const cursorPosition = 0;
102
- const label = '[Pasted text #1 +2 lines]';
103
- const result =
104
- existingText.slice(0, cursorPosition) + label + existingText.slice(cursorPosition);
105
- expect(result).toBe('[Pasted text #1 +2 lines]existing');
106
- });
107
-
108
- it('paste at end (cursor = text.length)', () => {
109
- const existingText = 'existing';
110
- const cursorPosition = existingText.length;
111
- const label = '[Pasted text #1 +2 lines]';
112
- const result =
113
- existingText.slice(0, cursorPosition) + label + existingText.slice(cursorPosition);
114
- expect(result).toBe('existing[Pasted text #1 +2 lines]');
115
- });
116
- });
@@ -1,46 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { expandPasteLabels } from '../paste-labels.js';
3
-
4
- describe('expandPasteLabels', () => {
5
- it('should expand single paste label', () => {
6
- const store = new Map([[1, 'line1\nline2\nline3']]);
7
- const result = expandPasteLabels('[Pasted text #1 +3 lines]', store);
8
- expect(result).toBe('line1\nline2\nline3');
9
- });
10
-
11
- it('should expand multiple paste labels', () => {
12
- const store = new Map([
13
- [1, 'first paste'],
14
- [2, 'second paste'],
15
- ]);
16
- const result = expandPasteLabels(
17
- 'before [Pasted text #1 +1 lines] middle [Pasted text #2 +1 lines] after',
18
- store,
19
- );
20
- expect(result).toBe('before first paste middle second paste after');
21
- });
22
-
23
- it('should return empty string for missing store entry', () => {
24
- const store = new Map<number, string>();
25
- const result = expandPasteLabels('[Pasted text #1 +3 lines]', store);
26
- expect(result).toBe('');
27
- });
28
-
29
- it('should preserve text without paste labels', () => {
30
- const store = new Map<number, string>();
31
- const result = expandPasteLabels('regular text without labels', store);
32
- expect(result).toBe('regular text without labels');
33
- });
34
-
35
- it('should handle label with different line counts', () => {
36
- const store = new Map([[1, 'a\nb']]);
37
- expect(expandPasteLabels('[Pasted text #1 +2 lines]', store)).toBe('a\nb');
38
- expect(expandPasteLabels('[Pasted text #1 +99 lines]', store)).toBe('a\nb');
39
- });
40
-
41
- it('should expand label without line count (debounce pending)', () => {
42
- const store = new Map([[1, 'partial paste']]);
43
- const result = expandPasteLabels('[Pasted text #1]', store);
44
- expect(result).toBe('partial paste');
45
- });
46
- });