@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,227 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { extractToolCalls, extractToolCallsWithDiff } from '../tool-call-extractor.js';
3
-
4
- describe('extractToolCalls', () => {
5
- it('returns empty array for empty history', () => {
6
- expect(extractToolCalls([], 0)).toEqual([]);
7
- });
8
-
9
- it('returns empty array when no assistant messages have toolCalls', () => {
10
- const history = [{ role: 'user' }, { role: 'assistant' }];
11
- expect(extractToolCalls(history, 0)).toEqual([]);
12
- });
13
-
14
- it('extracts tool call with string first argument', () => {
15
- const history = [
16
- {
17
- role: 'assistant',
18
- toolCalls: [{ function: { name: 'Read', arguments: '{"filePath":"/src/index.ts"}' } }],
19
- },
20
- ];
21
- expect(extractToolCalls(history, 0)).toEqual(['Read(/src/index.ts)']);
22
- });
23
-
24
- it('extracts multiple tool calls from one message', () => {
25
- const history = [
26
- {
27
- role: 'assistant',
28
- toolCalls: [
29
- { function: { name: 'Bash', arguments: '{"command":"ls -la"}' } },
30
- { function: { name: 'Glob', arguments: '{"pattern":"**/*.md"}' } },
31
- ],
32
- },
33
- ];
34
- expect(extractToolCalls(history, 0)).toEqual(['Bash(ls -la)', 'Glob(**/*.md)']);
35
- });
36
-
37
- it('extracts tool calls from multiple assistant messages', () => {
38
- const history = [
39
- {
40
- role: 'assistant',
41
- toolCalls: [{ function: { name: 'Read', arguments: '{"filePath":"a.ts"}' } }],
42
- },
43
- { role: 'user' },
44
- {
45
- role: 'assistant',
46
- toolCalls: [{ function: { name: 'Write', arguments: '{"filePath":"b.ts"}' } }],
47
- },
48
- ];
49
- expect(extractToolCalls(history, 0)).toEqual(['Read(a.ts)', 'Write(b.ts)']);
50
- });
51
-
52
- it('skips messages before startIndex', () => {
53
- const history = [
54
- {
55
- role: 'assistant',
56
- toolCalls: [{ function: { name: 'Old', arguments: '{"x":"skip"}' } }],
57
- },
58
- {
59
- role: 'assistant',
60
- toolCalls: [{ function: { name: 'New', arguments: '{"x":"keep"}' } }],
61
- },
62
- ];
63
- expect(extractToolCalls(history, 1)).toEqual(['New(keep)']);
64
- });
65
-
66
- it('truncates long argument values with middle ellipsis', () => {
67
- const longPath =
68
- '/Users/jungyoun/Documents/dev/robota/packages/agent-sdk/src/plugins/very-long-directory-name/file.ts';
69
- const history = [
70
- {
71
- role: 'assistant',
72
- toolCalls: [
73
- { function: { name: 'Read', arguments: JSON.stringify({ filePath: longPath }) } },
74
- ],
75
- },
76
- ];
77
- const result = extractToolCalls(history, 0);
78
- expect(result).toHaveLength(1);
79
- expect(result[0]).toContain('...');
80
- // Should keep the tail (file name visible)
81
- expect(result[0]).toContain('file.ts');
82
- // Total display length should be reasonable
83
- expect(result[0].length).toBeLessThanOrEqual(90);
84
- });
85
-
86
- it('handles non-string first argument (JSON stringified)', () => {
87
- const history = [
88
- {
89
- role: 'assistant',
90
- toolCalls: [{ function: { name: 'Tool', arguments: '{"count":42}' } }],
91
- },
92
- ];
93
- expect(extractToolCalls(history, 0)).toEqual(['Tool(42)']);
94
- });
95
-
96
- it('handles invalid JSON arguments gracefully', () => {
97
- const history = [
98
- {
99
- role: 'assistant',
100
- toolCalls: [{ function: { name: 'Tool', arguments: 'not json' } }],
101
- },
102
- ];
103
- expect(extractToolCalls(history, 0)).toEqual(['Tool(not json)']);
104
- });
105
-
106
- it('skips non-assistant messages', () => {
107
- const history = [
108
- { role: 'user' },
109
- { role: 'system' },
110
- {
111
- role: 'assistant',
112
- toolCalls: [{ function: { name: 'Bash', arguments: '{"command":"echo hi"}' } }],
113
- },
114
- ];
115
- expect(extractToolCalls(history, 0)).toEqual(['Bash(echo hi)']);
116
- });
117
- });
118
-
119
- describe('extractToolCallsWithDiff', () => {
120
- it('non-Edit tool has no diffLines', () => {
121
- const history = [
122
- {
123
- role: 'assistant',
124
- toolCalls: [{ function: { name: 'Read', arguments: '{"filePath":"/src/a.ts"}' } }],
125
- },
126
- ];
127
- const summaries = extractToolCallsWithDiff(history, 0);
128
- expect(summaries).toHaveLength(1);
129
- expect(summaries[0].line).toBe('Read(/src/a.ts)');
130
- expect(summaries[0].diffLines).toBeUndefined();
131
- expect(summaries[0].diffFile).toBeUndefined();
132
- });
133
-
134
- it('Edit tool includes diffLines and diffFile', () => {
135
- const args = JSON.stringify({
136
- file_path: '/src/index.ts',
137
- old_string: 'const a = 1;',
138
- new_string: 'const a = 2;',
139
- });
140
- const history = [
141
- {
142
- role: 'assistant',
143
- toolCalls: [{ function: { name: 'Edit', arguments: args } }],
144
- },
145
- ];
146
- const summaries = extractToolCallsWithDiff(history, 0);
147
- expect(summaries).toHaveLength(1);
148
- expect(summaries[0].diffFile).toBe('/src/index.ts');
149
- expect(summaries[0].diffLines).toEqual([
150
- { type: 'remove', text: 'const a = 1;', lineNumber: 1 },
151
- { type: 'add', text: 'const a = 2;', lineNumber: 1 },
152
- ]);
153
- });
154
-
155
- it('Edit tool with identical old/new has no diffLines', () => {
156
- const args = JSON.stringify({
157
- file_path: '/src/index.ts',
158
- old_string: 'same',
159
- new_string: 'same',
160
- });
161
- const history = [
162
- {
163
- role: 'assistant',
164
- toolCalls: [{ function: { name: 'Edit', arguments: args } }],
165
- },
166
- ];
167
- const summaries = extractToolCallsWithDiff(history, 0);
168
- expect(summaries).toHaveLength(1);
169
- expect(summaries[0].diffLines).toBeUndefined();
170
- expect(summaries[0].diffFile).toBeUndefined();
171
- });
172
-
173
- it('multiple tools including Edit: only Edit has diff', () => {
174
- const editArgs = JSON.stringify({
175
- file_path: '/src/main.ts',
176
- old_string: 'foo',
177
- new_string: 'bar',
178
- });
179
- const history = [
180
- {
181
- role: 'assistant',
182
- toolCalls: [
183
- { function: { name: 'Read', arguments: '{"filePath":"/src/a.ts"}' } },
184
- { function: { name: 'Edit', arguments: editArgs } },
185
- { function: { name: 'Bash', arguments: '{"command":"ls"}' } },
186
- ],
187
- },
188
- ];
189
- const summaries = extractToolCallsWithDiff(history, 0);
190
- expect(summaries).toHaveLength(3);
191
- expect(summaries[0].diffLines).toBeUndefined();
192
- expect(summaries[1].diffLines).toBeDefined();
193
- expect(summaries[1].diffFile).toBe('/src/main.ts');
194
- expect(summaries[2].diffLines).toBeUndefined();
195
- });
196
-
197
- it('extractToolCalls (plain) returns string[] without diff info', () => {
198
- const editArgs = JSON.stringify({
199
- file_path: '/src/index.ts',
200
- old_string: 'a',
201
- new_string: 'b',
202
- });
203
- const history = [
204
- {
205
- role: 'assistant',
206
- toolCalls: [{ function: { name: 'Edit', arguments: editArgs } }],
207
- },
208
- ];
209
- const result = extractToolCalls(history, 0);
210
- expect(result).toHaveLength(1);
211
- expect(typeof result[0]).toBe('string');
212
- // No diff properties on plain strings
213
- expect(result[0]).toContain('Edit(');
214
- });
215
-
216
- it('handles Edit tool with invalid JSON arguments gracefully', () => {
217
- const history = [
218
- {
219
- role: 'assistant',
220
- toolCalls: [{ function: { name: 'Edit', arguments: 'not json' } }],
221
- },
222
- ];
223
- const summaries = extractToolCallsWithDiff(history, 0);
224
- expect(summaries).toHaveLength(1);
225
- expect(summaries[0].diffLines).toBeUndefined();
226
- });
227
- });
@@ -1,104 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { buildToolDiffSummary } from '../tool-diff-summary.js';
3
- import type { IDiffLine } from '../edit-diff.js';
4
-
5
- describe('buildToolDiffSummary', () => {
6
- it('builds a markdown diff fenced body while preserving file metadata', () => {
7
- const lines: IDiffLine[] = [
8
- { type: 'hunk', lineNumber: 10, text: '@@ -10,2 +10,2 @@' },
9
- { type: 'context', lineNumber: 10, text: 'const before = true;' },
10
- { type: 'remove', lineNumber: 11, text: 'const value = false;' },
11
- { type: 'add', lineNumber: 11, text: 'const value = true;' },
12
- ];
13
-
14
- const summary = buildToolDiffSummary({ file: '/src/index.ts', lines });
15
-
16
- expect(summary.file).toBe('/src/index.ts');
17
- expect(summary.truncated).toBe(false);
18
- expect(summary.remainingLineCount).toBe(0);
19
- expect(summary.markdown).toBe(
20
- [
21
- '```diff',
22
- '@@ -10,2 +10,2 @@',
23
- ' 10 | const before = true;',
24
- '- 11 | const value = false;',
25
- '+ 11 | const value = true;',
26
- '```',
27
- ].join('\n'),
28
- );
29
- });
30
-
31
- it('truncates long diff bodies while keeping truncation metadata outside markdown', () => {
32
- const lines = Array.from({ length: 13 }, (_, index): IDiffLine => {
33
- const lineNumber = index + 1;
34
- return {
35
- type: index % 2 === 0 ? 'remove' : 'add',
36
- lineNumber,
37
- text: `line ${lineNumber}`,
38
- };
39
- });
40
-
41
- const summary = buildToolDiffSummary({ lines });
42
-
43
- expect(summary.truncated).toBe(true);
44
- expect(summary.remainingLineCount).toBe(3);
45
- expect(summary.markdown).toContain('- 1 | line 1');
46
- expect(summary.markdown).toContain('+ 10 | line 10');
47
- expect(summary.markdown).not.toContain('line 11');
48
- expect(summary.markdown).not.toContain('more lines');
49
- });
50
-
51
- it('preserves the first hunk when truncating multi-hunk diffs', () => {
52
- const lines: IDiffLine[] = [
53
- { type: 'hunk', lineNumber: 1, text: '@@ -1,4 +1,4 @@' },
54
- { type: 'context', lineNumber: 1, text: 'one' },
55
- { type: 'remove', lineNumber: 2, text: 'two-old' },
56
- { type: 'add', lineNumber: 2, text: 'two-new' },
57
- { type: 'context', lineNumber: 3, text: 'three' },
58
- { type: 'hunk', lineNumber: 20, text: '@@ -20,4 +20,4 @@' },
59
- { type: 'context', lineNumber: 20, text: 'twenty' },
60
- { type: 'remove', lineNumber: 21, text: 'twenty-one-old' },
61
- { type: 'add', lineNumber: 21, text: 'twenty-one-new' },
62
- { type: 'context', lineNumber: 22, text: 'twenty-two' },
63
- { type: 'hunk', lineNumber: 40, text: '@@ -40,4 +40,4 @@' },
64
- { type: 'context', lineNumber: 40, text: 'forty' },
65
- { type: 'remove', lineNumber: 41, text: 'forty-one-old' },
66
- { type: 'add', lineNumber: 41, text: 'forty-one-new' },
67
- { type: 'context', lineNumber: 42, text: 'forty-two' },
68
- ];
69
-
70
- const summary = buildToolDiffSummary({ lines });
71
-
72
- expect(summary.truncated).toBe(true);
73
- expect(summary.remainingLineCount).toBe(5);
74
- expect(summary.markdown).toContain('@@ -1,4 +1,4 @@');
75
- expect(summary.markdown).toContain('two-new');
76
- expect(summary.markdown).toContain('@@ -20,4 +20,4 @@');
77
- expect(summary.markdown).not.toContain('@@ -40,4 +40,4 @@');
78
- });
79
-
80
- it('counts omitted lines from the actual rendered diff lines', () => {
81
- const lines: IDiffLine[] = [
82
- { type: 'hunk', lineNumber: 1, text: '@@ -1,5 +1,5 @@' },
83
- { type: 'context', lineNumber: 1, text: 'one' },
84
- { type: 'remove', lineNumber: 2, text: 'two-old' },
85
- { type: 'add', lineNumber: 2, text: 'two-new' },
86
- { type: 'context', lineNumber: 3, text: 'three' },
87
- { type: 'context', lineNumber: 4, text: 'four' },
88
- { type: 'hunk', lineNumber: 20, text: '@@ -20,4 +20,4 @@' },
89
- { type: 'context', lineNumber: 20, text: 'twenty' },
90
- { type: 'remove', lineNumber: 21, text: 'twenty-one-old' },
91
- { type: 'add', lineNumber: 21, text: 'twenty-one-new' },
92
- { type: 'context', lineNumber: 22, text: 'twenty-two' },
93
- { type: 'hunk', lineNumber: 40, text: '@@ -40,1 +40,1 @@' },
94
- { type: 'context', lineNumber: 40, text: 'forty' },
95
- ];
96
-
97
- const summary = buildToolDiffSummary({ lines });
98
-
99
- expect(summary.truncated).toBe(true);
100
- expect(summary.remainingLineCount).toBe(7);
101
- expect(summary.markdown).toContain('@@ -1,5 +1,5 @@');
102
- expect(summary.markdown).not.toContain('@@ -20,4 +20,4 @@');
103
- });
104
- });
@@ -1,153 +0,0 @@
1
- /**
2
- * Generate diff lines from Edit tool's old_string and new_string.
3
- * Includes absolute line numbers and optional context lines from the file.
4
- */
5
-
6
- import { readFileSync } from 'node:fs';
7
-
8
- import type { TUniversalValue } from '@robota-sdk/agent-core';
9
-
10
- export interface IDiffLine {
11
- type: 'add' | 'remove' | 'context' | 'hunk';
12
- text: string;
13
- /** Absolute line number in the file */
14
- lineNumber: number;
15
- }
16
-
17
- /** Number of context lines to show before and after the change */
18
- const CONTEXT_LINES = 3;
19
-
20
- /**
21
- * Generate diff lines from old and new strings with absolute line numbers.
22
- * @param oldStr - The text being replaced
23
- * @param newStr - The replacement text
24
- * @param startLine - The 1-based line number where oldStr starts in the file
25
- */
26
- export function generateDiffLines(
27
- oldStr: string,
28
- newStr: string,
29
- startLine: number = 1,
30
- ): IDiffLine[] {
31
- if (oldStr === newStr) return [];
32
-
33
- const lines: IDiffLine[] = [];
34
-
35
- const oldLines = oldStr.split('\n');
36
- const newLines = newStr.split('\n');
37
-
38
- for (let i = 0; i < oldLines.length; i++) {
39
- lines.push({ type: 'remove', text: oldLines[i], lineNumber: startLine + i });
40
- }
41
- for (let i = 0; i < newLines.length; i++) {
42
- lines.push({ type: 'add', text: newLines[i], lineNumber: startLine + i });
43
- }
44
-
45
- return lines;
46
- }
47
-
48
- /**
49
- * Generate diff lines with context from the modified file.
50
- * Reads the file (already modified) to get surrounding lines.
51
- */
52
- export function generateDiffLinesWithContext(
53
- oldStr: string,
54
- newStr: string,
55
- startLine: number,
56
- filePath: string,
57
- ): IDiffLine[] {
58
- if (oldStr === newStr) return [];
59
-
60
- const diffLines = generateDiffLines(oldStr, newStr, startLine);
61
-
62
- // Read modified file for context lines
63
- let fileLines: string[];
64
- try {
65
- fileLines = readFileSync(filePath, 'utf-8').split('\n');
66
- } catch {
67
- return diffLines; // Can't read file — return without context
68
- }
69
-
70
- // Context BEFORE: lines before startLine in the file
71
- const contextStart = Math.max(0, startLine - 1 - CONTEXT_LINES);
72
- const beforeContext: IDiffLine[] = [];
73
- for (let i = contextStart; i < startLine - 1; i++) {
74
- if (i < fileLines.length) {
75
- beforeContext.push({ type: 'context', text: fileLines[i], lineNumber: i + 1 });
76
- }
77
- }
78
-
79
- // Context AFTER: lines after the new content in the modified file
80
- const newLineCount = newStr.split('\n').length;
81
- const afterStart = startLine - 1 + newLineCount;
82
- const afterContext: IDiffLine[] = [];
83
- for (let i = afterStart; i < afterStart + CONTEXT_LINES; i++) {
84
- if (i < fileLines.length) {
85
- afterContext.push({ type: 'context', text: fileLines[i], lineNumber: i + 1 });
86
- }
87
- }
88
-
89
- const hunkStart =
90
- beforeContext[0]?.lineNumber ??
91
- diffLines[0]?.lineNumber ??
92
- afterContext[0]?.lineNumber ??
93
- startLine;
94
- const oldLineCount = oldStr.split('\n').length;
95
- const newLineCountInHunk = newStr.split('\n').length;
96
- const oldHunkLineCount = beforeContext.length + oldLineCount + afterContext.length;
97
- const newHunkLineCount = beforeContext.length + newLineCountInHunk + afterContext.length;
98
-
99
- return [
100
- {
101
- type: 'hunk',
102
- text: `@@ -${hunkStart},${oldHunkLineCount} +${hunkStart},${newHunkLineCount} @@`,
103
- lineNumber: hunkStart,
104
- },
105
- ...beforeContext,
106
- ...diffLines,
107
- ...afterContext,
108
- ];
109
- }
110
-
111
- /**
112
- * Extract Edit tool diff info from tool arguments.
113
- * @param toolName - Tool name (must be 'Edit')
114
- * @param toolArgs - Tool arguments (filePath, oldString, newString)
115
- * @param startLine - Start line number from Edit tool result (optional)
116
- */
117
- export function extractEditDiff(
118
- toolName: string,
119
- toolArgs?: Record<string, TUniversalValue>,
120
- startLine?: number,
121
- ): { file: string; lines: IDiffLine[] } | null {
122
- if (toolName !== 'Edit' || !toolArgs) return null;
123
-
124
- const filePath = toolArgs.file_path ?? toolArgs.filePath;
125
- const oldStr = toolArgs.old_string ?? toolArgs.oldString;
126
- const newStr = toolArgs.new_string ?? toolArgs.newString;
127
-
128
- if (typeof filePath !== 'string') return null;
129
- if (typeof oldStr !== 'string' || typeof newStr !== 'string') return null;
130
-
131
- // Resolve start line: from tool result, or by finding newString in the modified file
132
- let sl = startLine ?? 0;
133
- if (!sl) {
134
- try {
135
- const fileContent = readFileSync(filePath, 'utf-8');
136
- const idx = fileContent.indexOf(newStr);
137
- if (idx >= 0) {
138
- sl = fileContent.substring(0, idx).split('\n').length;
139
- } else {
140
- sl = 1;
141
- }
142
- } catch {
143
- sl = 1;
144
- }
145
- }
146
-
147
- // Always try to include context from the file
148
- const lines = generateDiffLinesWithContext(oldStr, newStr, sl, filePath);
149
-
150
- if (lines.length === 0) return null;
151
-
152
- return { file: filePath, lines };
153
- }
@@ -1,9 +0,0 @@
1
- const PASTE_LABEL_RE = /\[Pasted text #(\d+)(?: \+\d+ lines)?\]/g;
2
-
3
- /**
4
- * Replace paste label placeholders with their original content from the store.
5
- * Labels that have no matching store entry are replaced with empty string.
6
- */
7
- export function expandPasteLabels(text: string, store: Map<number, string>): string {
8
- return text.replace(PASTE_LABEL_RE, (_, id: string) => store.get(Number(id)) ?? '');
9
- }
@@ -1,92 +0,0 @@
1
- /**
2
- * Extracts tool call summaries from session history messages.
3
- * Pure function — no side effects, no framework dependencies.
4
- */
5
-
6
- import { extractEditDiff } from './edit-diff.js';
7
-
8
- import type { IDiffLine } from './edit-diff.js';
9
- import type { TUniversalValue } from '@robota-sdk/agent-core';
10
-
11
- const TOOL_ARG_MAX_LENGTH = 80;
12
- const TAIL_KEEP = 30;
13
- const ELLIPSIS_LEN = 3;
14
-
15
- interface IHistoryMessage {
16
- role: string;
17
- toolCalls?: Array<{
18
- function: { name: string; arguments: string };
19
- }>;
20
- }
21
-
22
- /** A tool call summary with optional diff for Edit tools */
23
- export interface IToolCallSummary {
24
- line: string;
25
- diffLines?: IDiffLine[];
26
- diffFile?: string;
27
- }
28
-
29
- /**
30
- * Extract tool call display lines from history messages.
31
- * Format: `ToolName(firstArgValue)` — first argument value truncated to 80 chars.
32
- * Edit tools include diff information.
33
- */
34
- export function extractToolCalls(history: IHistoryMessage[], startIndex: number): string[] {
35
- return extractToolCallsWithDiff(history, startIndex).map((s) => s.line);
36
- }
37
-
38
- /**
39
- * Extract tool call summaries with diff info from history messages.
40
- */
41
- export function extractToolCallsWithDiff(
42
- history: IHistoryMessage[],
43
- startIndex: number,
44
- ): IToolCallSummary[] {
45
- const summaries: IToolCallSummary[] = [];
46
- for (let i = startIndex; i < history.length; i++) {
47
- const msg = history[i];
48
- if (msg.role === 'assistant' && msg.toolCalls) {
49
- for (const tc of msg.toolCalls) {
50
- const value = parseFirstArgValue(tc.function.arguments);
51
- const truncated =
52
- value.length > TOOL_ARG_MAX_LENGTH
53
- ? value.slice(0, TOOL_ARG_MAX_LENGTH - TAIL_KEEP - ELLIPSIS_LEN) +
54
- '...' +
55
- value.slice(-TAIL_KEEP)
56
- : value;
57
-
58
- const summary: IToolCallSummary = {
59
- line: `${tc.function.name}(${truncated})`,
60
- };
61
-
62
- // Extract diff for Edit tool
63
- if (tc.function.name === 'Edit') {
64
- try {
65
- const args = JSON.parse(tc.function.arguments) as Record<string, TUniversalValue>;
66
- const diff = extractEditDiff('Edit', args);
67
- if (diff) {
68
- summary.diffLines = diff.lines;
69
- summary.diffFile = diff.file;
70
- }
71
- } catch {
72
- // ignore parse errors
73
- }
74
- }
75
-
76
- summaries.push(summary);
77
- }
78
- }
79
- }
80
- return summaries;
81
- }
82
-
83
- /** Parse the first argument value from a JSON arguments string. */
84
- function parseFirstArgValue(argsJson: string): string {
85
- try {
86
- const parsed = JSON.parse(argsJson) as Record<string, TUniversalValue>;
87
- const firstVal = Object.values(parsed)[0];
88
- return typeof firstVal === 'string' ? firstVal : JSON.stringify(firstVal);
89
- } catch {
90
- return argsJson;
91
- }
92
- }
@@ -1,75 +0,0 @@
1
- import type { IDiffLine } from './edit-diff.js';
2
-
3
- const MAX_DIFF_LINES = 12;
4
- const TRUNCATED_SHOW = 10;
5
-
6
- export interface IToolDiffSummaryInput {
7
- file?: string;
8
- lines: readonly IDiffLine[];
9
- }
10
-
11
- export interface IToolDiffSummary {
12
- file?: string;
13
- markdown: string;
14
- truncated: boolean;
15
- remainingLineCount: number;
16
- }
17
-
18
- export function buildToolDiffSummary(input: IToolDiffSummaryInput): IToolDiffSummary {
19
- const visibleLines =
20
- input.lines.length > MAX_DIFF_LINES ? selectVisibleDiffLines(input.lines) : input.lines;
21
- const lineNumberWidth = Math.max(...visibleLines.map((line) => line.lineNumber), 0).toString()
22
- .length;
23
- const body = visibleLines.map((line) => formatDiffLine(line, lineNumberWidth));
24
- const truncated = input.lines.length > MAX_DIFF_LINES;
25
-
26
- return {
27
- file: input.file,
28
- markdown: ['```diff', ...body, '```'].join('\n'),
29
- truncated,
30
- remainingLineCount: truncated ? input.lines.length - visibleLines.length : 0,
31
- };
32
- }
33
-
34
- function formatDiffLine(line: IDiffLine, lineNumberWidth: number): string {
35
- if (line.type === 'hunk') return line.text;
36
- const lineNumber = line.lineNumber.toString().padStart(lineNumberWidth, ' ');
37
- if (line.type === 'remove') return `- ${lineNumber} | ${line.text}`;
38
- if (line.type === 'add') return `+ ${lineNumber} | ${line.text}`;
39
- return ` ${lineNumber} | ${line.text}`;
40
- }
41
-
42
- function selectVisibleDiffLines(lines: readonly IDiffLine[]): readonly IDiffLine[] {
43
- const groups = groupByHunk(lines);
44
- const visible: IDiffLine[] = [];
45
-
46
- for (const group of groups) {
47
- if (visible.length === 0 && group.length > TRUNCATED_SHOW) {
48
- return group.slice(0, TRUNCATED_SHOW);
49
- }
50
- if (visible.length + group.length > TRUNCATED_SHOW) break;
51
- visible.push(...group);
52
- }
53
-
54
- return visible.length > 0 ? visible : lines.slice(0, TRUNCATED_SHOW);
55
- }
56
-
57
- function groupByHunk(lines: readonly IDiffLine[]): IDiffLine[][] {
58
- const groups: IDiffLine[][] = [];
59
- let current: IDiffLine[] = [];
60
-
61
- for (const line of lines) {
62
- if (line.type === 'hunk' && current.length > 0) {
63
- groups.push(current);
64
- current = [line];
65
- continue;
66
- }
67
- current.push(line);
68
- }
69
-
70
- if (current.length > 0) {
71
- groups.push(current);
72
- }
73
-
74
- return groups;
75
- }