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