@renxqoo/renx-code 0.0.3 → 0.0.4

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 (306) hide show
  1. package/README.md +58 -223
  2. package/bin/renx.cjs +34 -0
  3. package/package.json +27 -83
  4. package/src/App.tsx +297 -0
  5. package/src/agent/runtime/event-format.ts +258 -0
  6. package/src/agent/runtime/model-types.ts +13 -0
  7. package/src/agent/runtime/runtime.context-usage.test.ts +193 -0
  8. package/src/agent/runtime/runtime.error-handling.test.ts +236 -0
  9. package/src/agent/runtime/runtime.simple.test.ts +16 -0
  10. package/src/agent/runtime/runtime.test.ts +293 -0
  11. package/src/agent/runtime/runtime.ts +881 -0
  12. package/src/agent/runtime/runtime.usage-forwarding.test.ts +229 -0
  13. package/src/agent/runtime/source-modules.test.ts +57 -0
  14. package/src/agent/runtime/source-modules.ts +353 -0
  15. package/src/agent/runtime/tool-call-buffer.test.ts +65 -0
  16. package/src/agent/runtime/tool-call-buffer.ts +60 -0
  17. package/src/agent/runtime/tool-confirmation.test.ts +56 -0
  18. package/src/agent/runtime/tool-confirmation.ts +15 -0
  19. package/src/agent/runtime/types.ts +99 -0
  20. package/src/commands/slash-commands.test.ts +216 -0
  21. package/src/commands/slash-commands.ts +64 -0
  22. package/src/components/chat/assistant-reply.test.tsx +47 -0
  23. package/src/components/chat/assistant-reply.tsx +136 -0
  24. package/src/components/chat/assistant-segment.test.ts +99 -0
  25. package/src/components/chat/assistant-segment.tsx +125 -0
  26. package/src/components/chat/assistant-tool-group.tsx +900 -0
  27. package/src/components/chat/code-block.test.tsx +206 -0
  28. package/src/components/chat/code-block.tsx +313 -0
  29. package/src/components/chat/prompt-card.tsx +81 -0
  30. package/src/components/chat/segment-groups.test.ts +52 -0
  31. package/src/components/chat/segment-groups.ts +106 -0
  32. package/src/components/chat/turn-item.tsx +39 -0
  33. package/src/components/conversation-panel.tsx +43 -0
  34. package/src/components/file-mention-menu.tsx +77 -0
  35. package/src/components/file-picker-dialog.tsx +206 -0
  36. package/src/components/footer-hints.tsx +75 -0
  37. package/src/components/model-picker-dialog.tsx +248 -0
  38. package/src/components/prompt.tsx +233 -0
  39. package/src/components/slash-command-menu.tsx +65 -0
  40. package/src/components/tool-confirm-dialog-content.test.ts +103 -0
  41. package/src/components/tool-confirm-dialog-content.ts +186 -0
  42. package/src/components/tool-confirm-dialog.tsx +187 -0
  43. package/src/components/tool-display-config.ts +119 -0
  44. package/src/context-usage-regressions.test.ts +26 -0
  45. package/src/files/attachment-capabilities.test.ts +30 -0
  46. package/src/files/attachment-capabilities.ts +50 -0
  47. package/src/files/attachment-content.ts +153 -0
  48. package/src/files/file-mention-query.test.ts +34 -0
  49. package/src/files/file-mention-query.ts +32 -0
  50. package/src/files/prompt-display.ts +13 -0
  51. package/src/files/types.ts +5 -0
  52. package/src/files/workspace-files.ts +63 -0
  53. package/src/hooks/agent-event-handlers.test.ts +207 -0
  54. package/src/hooks/agent-event-handlers.ts +196 -0
  55. package/src/hooks/chat-local-replies.fixed.test.ts +119 -0
  56. package/src/hooks/chat-local-replies.test.ts +153 -0
  57. package/src/hooks/chat-local-replies.ts +63 -0
  58. package/src/hooks/turn-updater.test.ts +70 -0
  59. package/src/hooks/turn-updater.ts +166 -0
  60. package/src/hooks/use-agent-chat.context.test.ts +10 -0
  61. package/src/hooks/use-agent-chat.status.test.ts +14 -0
  62. package/src/hooks/use-agent-chat.test.ts +80 -0
  63. package/src/hooks/use-agent-chat.ts +621 -0
  64. package/src/hooks/use-file-mention-menu.ts +196 -0
  65. package/src/hooks/use-file-picker.ts +185 -0
  66. package/src/hooks/use-model-picker.ts +196 -0
  67. package/src/hooks/use-slash-command-menu.ts +154 -0
  68. package/src/index.tsx +55 -0
  69. package/src/runtime/clipboard.test.ts +43 -0
  70. package/src/runtime/clipboard.ts +89 -0
  71. package/src/runtime/exit.test.ts +177 -0
  72. package/src/runtime/exit.ts +98 -0
  73. package/src/runtime/runtime-support.test.ts +31 -0
  74. package/src/runtime/terminal-theme.test.ts +55 -0
  75. package/src/runtime/terminal-theme.ts +196 -0
  76. package/src/types/chat.ts +32 -0
  77. package/src/types/message-content.ts +48 -0
  78. package/src/ui/open-code-theme.ts +176 -0
  79. package/src/ui/opencode-markdown.ts +211 -0
  80. package/src/ui/theme.simple.test.ts +52 -0
  81. package/src/ui/theme.test.ts +151 -0
  82. package/src/ui/theme.ts +152 -0
  83. package/src/utils/time.test.ts +144 -0
  84. package/src/utils/time.ts +7 -0
  85. package/tsconfig.json +30 -0
  86. package/LICENSE +0 -21
  87. package/dist/App.d.ts +0 -2
  88. package/dist/App.d.ts.map +0 -1
  89. package/dist/App.js +0 -170
  90. package/dist/App.js.map +0 -1
  91. package/dist/agent/prompts/system.d.ts +0 -24
  92. package/dist/agent/prompts/system.d.ts.map +0 -1
  93. package/dist/agent/prompts/system.js +0 -222
  94. package/dist/agent/prompts/system.js.map +0 -1
  95. package/dist/agent/runtime/event-format.d.ts +0 -17
  96. package/dist/agent/runtime/event-format.d.ts.map +0 -1
  97. package/dist/agent/runtime/event-format.js +0 -194
  98. package/dist/agent/runtime/event-format.js.map +0 -1
  99. package/dist/agent/runtime/model-types.d.ts +0 -13
  100. package/dist/agent/runtime/model-types.d.ts.map +0 -1
  101. package/dist/agent/runtime/model-types.js +0 -1
  102. package/dist/agent/runtime/model-types.js.map +0 -1
  103. package/dist/agent/runtime/runtime.d.ts +0 -16
  104. package/dist/agent/runtime/runtime.d.ts.map +0 -1
  105. package/dist/agent/runtime/runtime.js +0 -691
  106. package/dist/agent/runtime/runtime.js.map +0 -1
  107. package/dist/agent/runtime/source-modules.d.ts +0 -176
  108. package/dist/agent/runtime/source-modules.d.ts.map +0 -1
  109. package/dist/agent/runtime/source-modules.js +0 -110
  110. package/dist/agent/runtime/source-modules.js.map +0 -1
  111. package/dist/agent/runtime/tool-call-buffer.d.ts +0 -12
  112. package/dist/agent/runtime/tool-call-buffer.d.ts.map +0 -1
  113. package/dist/agent/runtime/tool-call-buffer.js +0 -48
  114. package/dist/agent/runtime/tool-call-buffer.js.map +0 -1
  115. package/dist/agent/runtime/tool-confirmation.d.ts +0 -3
  116. package/dist/agent/runtime/tool-confirmation.d.ts.map +0 -1
  117. package/dist/agent/runtime/tool-confirmation.js +0 -9
  118. package/dist/agent/runtime/tool-confirmation.js.map +0 -1
  119. package/dist/agent/runtime/types.d.ts +0 -86
  120. package/dist/agent/runtime/types.d.ts.map +0 -1
  121. package/dist/agent/runtime/types.js +0 -1
  122. package/dist/agent/runtime/types.js.map +0 -1
  123. package/dist/cli.d.ts +0 -3
  124. package/dist/cli.d.ts.map +0 -1
  125. package/dist/cli.js +0 -12
  126. package/dist/cli.js.map +0 -1
  127. package/dist/commands/slash-commands.d.ts +0 -11
  128. package/dist/commands/slash-commands.d.ts.map +0 -1
  129. package/dist/commands/slash-commands.js +0 -48
  130. package/dist/commands/slash-commands.js.map +0 -1
  131. package/dist/components/chat/assistant-reply.d.ts +0 -13
  132. package/dist/components/chat/assistant-reply.d.ts.map +0 -1
  133. package/dist/components/chat/assistant-reply.js +0 -78
  134. package/dist/components/chat/assistant-reply.js.map +0 -1
  135. package/dist/components/chat/assistant-segment.d.ts +0 -8
  136. package/dist/components/chat/assistant-segment.d.ts.map +0 -1
  137. package/dist/components/chat/assistant-segment.js +0 -54
  138. package/dist/components/chat/assistant-segment.js.map +0 -1
  139. package/dist/components/chat/assistant-tool-group.d.ts +0 -7
  140. package/dist/components/chat/assistant-tool-group.d.ts.map +0 -1
  141. package/dist/components/chat/assistant-tool-group.js +0 -695
  142. package/dist/components/chat/assistant-tool-group.js.map +0 -1
  143. package/dist/components/chat/code-block.d.ts +0 -16
  144. package/dist/components/chat/code-block.d.ts.map +0 -1
  145. package/dist/components/chat/code-block.js +0 -194
  146. package/dist/components/chat/code-block.js.map +0 -1
  147. package/dist/components/chat/prompt-card.d.ts +0 -9
  148. package/dist/components/chat/prompt-card.d.ts.map +0 -1
  149. package/dist/components/chat/prompt-card.js +0 -18
  150. package/dist/components/chat/prompt-card.js.map +0 -1
  151. package/dist/components/chat/segment-groups.d.ts +0 -24
  152. package/dist/components/chat/segment-groups.d.ts.map +0 -1
  153. package/dist/components/chat/segment-groups.js +0 -69
  154. package/dist/components/chat/segment-groups.js.map +0 -1
  155. package/dist/components/chat/turn-item.d.ts +0 -9
  156. package/dist/components/chat/turn-item.d.ts.map +0 -1
  157. package/dist/components/chat/turn-item.js +0 -11
  158. package/dist/components/chat/turn-item.js.map +0 -1
  159. package/dist/components/conversation-panel.d.ts +0 -8
  160. package/dist/components/conversation-panel.d.ts.map +0 -1
  161. package/dist/components/conversation-panel.js +0 -8
  162. package/dist/components/conversation-panel.js.map +0 -1
  163. package/dist/components/file-mention-menu.d.ts +0 -11
  164. package/dist/components/file-mention-menu.d.ts.map +0 -1
  165. package/dist/components/file-mention-menu.js +0 -15
  166. package/dist/components/file-mention-menu.js.map +0 -1
  167. package/dist/components/file-picker-dialog.d.ts +0 -21
  168. package/dist/components/file-picker-dialog.d.ts.map +0 -1
  169. package/dist/components/file-picker-dialog.js +0 -48
  170. package/dist/components/file-picker-dialog.js.map +0 -1
  171. package/dist/components/footer-hints.d.ts +0 -7
  172. package/dist/components/footer-hints.d.ts.map +0 -1
  173. package/dist/components/footer-hints.js +0 -29
  174. package/dist/components/footer-hints.js.map +0 -1
  175. package/dist/components/model-picker-dialog.d.ts +0 -20
  176. package/dist/components/model-picker-dialog.d.ts.map +0 -1
  177. package/dist/components/model-picker-dialog.js +0 -72
  178. package/dist/components/model-picker-dialog.js.map +0 -1
  179. package/dist/components/prompt.d.ts +0 -18
  180. package/dist/components/prompt.d.ts.map +0 -1
  181. package/dist/components/prompt.js +0 -96
  182. package/dist/components/prompt.js.map +0 -1
  183. package/dist/components/slash-command-menu.d.ts +0 -9
  184. package/dist/components/slash-command-menu.d.ts.map +0 -1
  185. package/dist/components/slash-command-menu.js +0 -20
  186. package/dist/components/slash-command-menu.js.map +0 -1
  187. package/dist/components/tool-confirm-dialog-content.d.ts +0 -15
  188. package/dist/components/tool-confirm-dialog-content.d.ts.map +0 -1
  189. package/dist/components/tool-confirm-dialog-content.js +0 -143
  190. package/dist/components/tool-confirm-dialog-content.js.map +0 -1
  191. package/dist/components/tool-confirm-dialog.d.ts +0 -12
  192. package/dist/components/tool-confirm-dialog.d.ts.map +0 -1
  193. package/dist/components/tool-confirm-dialog.js +0 -21
  194. package/dist/components/tool-confirm-dialog.js.map +0 -1
  195. package/dist/components/tool-display-config.d.ts +0 -11
  196. package/dist/components/tool-display-config.d.ts.map +0 -1
  197. package/dist/components/tool-display-config.js +0 -94
  198. package/dist/components/tool-display-config.js.map +0 -1
  199. package/dist/config/paths.d.ts +0 -7
  200. package/dist/config/paths.d.ts.map +0 -1
  201. package/dist/config/paths.js +0 -24
  202. package/dist/config/paths.js.map +0 -1
  203. package/dist/files/attachment-capabilities.d.ts +0 -19
  204. package/dist/files/attachment-capabilities.d.ts.map +0 -1
  205. package/dist/files/attachment-capabilities.js +0 -26
  206. package/dist/files/attachment-capabilities.js.map +0 -1
  207. package/dist/files/attachment-content.d.ts +0 -5
  208. package/dist/files/attachment-content.d.ts.map +0 -1
  209. package/dist/files/attachment-content.js +0 -117
  210. package/dist/files/attachment-content.js.map +0 -1
  211. package/dist/files/file-mention-query.d.ts +0 -9
  212. package/dist/files/file-mention-query.d.ts.map +0 -1
  213. package/dist/files/file-mention-query.js +0 -23
  214. package/dist/files/file-mention-query.js.map +0 -1
  215. package/dist/files/prompt-display.d.ts +0 -3
  216. package/dist/files/prompt-display.d.ts.map +0 -1
  217. package/dist/files/prompt-display.js +0 -11
  218. package/dist/files/prompt-display.js.map +0 -1
  219. package/dist/files/types.d.ts +0 -6
  220. package/dist/files/types.d.ts.map +0 -1
  221. package/dist/files/types.js +0 -1
  222. package/dist/files/types.js.map +0 -1
  223. package/dist/files/workspace-files.d.ts +0 -3
  224. package/dist/files/workspace-files.d.ts.map +0 -1
  225. package/dist/files/workspace-files.js +0 -48
  226. package/dist/files/workspace-files.js.map +0 -1
  227. package/dist/hooks/agent-event-handlers.d.ts +0 -11
  228. package/dist/hooks/agent-event-handlers.d.ts.map +0 -1
  229. package/dist/hooks/agent-event-handlers.js +0 -137
  230. package/dist/hooks/agent-event-handlers.js.map +0 -1
  231. package/dist/hooks/chat-local-replies.d.ts +0 -9
  232. package/dist/hooks/chat-local-replies.d.ts.map +0 -1
  233. package/dist/hooks/chat-local-replies.js +0 -54
  234. package/dist/hooks/chat-local-replies.js.map +0 -1
  235. package/dist/hooks/turn-updater.d.ts +0 -9
  236. package/dist/hooks/turn-updater.d.ts.map +0 -1
  237. package/dist/hooks/turn-updater.js +0 -103
  238. package/dist/hooks/turn-updater.js.map +0 -1
  239. package/dist/hooks/use-agent-chat.d.ts +0 -29
  240. package/dist/hooks/use-agent-chat.d.ts.map +0 -1
  241. package/dist/hooks/use-agent-chat.js +0 -455
  242. package/dist/hooks/use-agent-chat.js.map +0 -1
  243. package/dist/hooks/use-file-mention-menu.d.ts +0 -22
  244. package/dist/hooks/use-file-mention-menu.d.ts.map +0 -1
  245. package/dist/hooks/use-file-mention-menu.js +0 -137
  246. package/dist/hooks/use-file-mention-menu.js.map +0 -1
  247. package/dist/hooks/use-file-picker.d.ts +0 -21
  248. package/dist/hooks/use-file-picker.d.ts.map +0 -1
  249. package/dist/hooks/use-file-picker.js +0 -145
  250. package/dist/hooks/use-file-picker.js.map +0 -1
  251. package/dist/hooks/use-model-picker.d.ts +0 -23
  252. package/dist/hooks/use-model-picker.d.ts.map +0 -1
  253. package/dist/hooks/use-model-picker.js +0 -151
  254. package/dist/hooks/use-model-picker.js.map +0 -1
  255. package/dist/hooks/use-slash-command-menu.d.ts +0 -19
  256. package/dist/hooks/use-slash-command-menu.d.ts.map +0 -1
  257. package/dist/hooks/use-slash-command-menu.js +0 -101
  258. package/dist/hooks/use-slash-command-menu.js.map +0 -1
  259. package/dist/index.d.ts +0 -2
  260. package/dist/index.d.ts.map +0 -1
  261. package/dist/index.js +0 -6
  262. package/dist/index.js.map +0 -1
  263. package/dist/run-cli-app.d.ts +0 -2
  264. package/dist/run-cli-app.d.ts.map +0 -1
  265. package/dist/run-cli-app.js +0 -41
  266. package/dist/run-cli-app.js.map +0 -1
  267. package/dist/runtime/clipboard.d.ts +0 -10
  268. package/dist/runtime/clipboard.d.ts.map +0 -1
  269. package/dist/runtime/clipboard.js +0 -64
  270. package/dist/runtime/clipboard.js.map +0 -1
  271. package/dist/runtime/exit.d.ts +0 -7
  272. package/dist/runtime/exit.d.ts.map +0 -1
  273. package/dist/runtime/exit.js +0 -85
  274. package/dist/runtime/exit.js.map +0 -1
  275. package/dist/runtime/runtime-support.d.ts +0 -4
  276. package/dist/runtime/runtime-support.d.ts.map +0 -1
  277. package/dist/runtime/runtime-support.js +0 -19
  278. package/dist/runtime/runtime-support.js.map +0 -1
  279. package/dist/runtime/terminal-theme.d.ts +0 -25
  280. package/dist/runtime/terminal-theme.d.ts.map +0 -1
  281. package/dist/runtime/terminal-theme.js +0 -148
  282. package/dist/runtime/terminal-theme.js.map +0 -1
  283. package/dist/types/chat.d.ts +0 -29
  284. package/dist/types/chat.d.ts.map +0 -1
  285. package/dist/types/chat.js +0 -1
  286. package/dist/types/chat.js.map +0 -1
  287. package/dist/types/message-content.d.ts +0 -38
  288. package/dist/types/message-content.d.ts.map +0 -1
  289. package/dist/types/message-content.js +0 -1
  290. package/dist/types/message-content.js.map +0 -1
  291. package/dist/ui/open-code-theme.d.ts +0 -58
  292. package/dist/ui/open-code-theme.d.ts.map +0 -1
  293. package/dist/ui/open-code-theme.js +0 -113
  294. package/dist/ui/open-code-theme.js.map +0 -1
  295. package/dist/ui/opencode-markdown.d.ts +0 -7
  296. package/dist/ui/opencode-markdown.d.ts.map +0 -1
  297. package/dist/ui/opencode-markdown.js +0 -169
  298. package/dist/ui/opencode-markdown.js.map +0 -1
  299. package/dist/ui/theme.d.ts +0 -68
  300. package/dist/ui/theme.d.ts.map +0 -1
  301. package/dist/ui/theme.js +0 -80
  302. package/dist/ui/theme.js.map +0 -1
  303. package/dist/utils/time.d.ts +0 -2
  304. package/dist/utils/time.d.ts.map +0 -1
  305. package/dist/utils/time.js +0 -7
  306. package/dist/utils/time.js.map +0 -1
@@ -0,0 +1,193 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ vi.mock('./tool-confirmation', () => ({
4
+ resolveToolConfirmDecision: vi.fn(),
5
+ }));
6
+
7
+ vi.mock('./source-modules', () => ({
8
+ getSourceModules: vi.fn(),
9
+ resolveWorkspaceRoot: vi.fn(),
10
+ }));
11
+
12
+ vi.mock('../../../../src/agent/prompts/system', () => ({
13
+ buildSystemPrompt: vi.fn(() => 'Test system prompt'),
14
+ }));
15
+
16
+ import { disposeAgentRuntime, runAgentPrompt } from './runtime';
17
+ import * as sourceModules from './source-modules';
18
+ import type { AgentContextUsageEvent, AgentEventHandlers } from './types';
19
+
20
+ describe('runAgentPrompt context usage forwarding', () => {
21
+ const mockGetSourceModules = sourceModules.getSourceModules as unknown as ReturnType<
22
+ typeof vi.fn
23
+ >;
24
+ const mockResolveWorkspaceRoot = sourceModules.resolveWorkspaceRoot as unknown as ReturnType<
25
+ typeof vi.fn
26
+ >;
27
+ const originalApiKey = process.env.TEST_API_KEY;
28
+
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ process.env.TEST_API_KEY = 'test-key';
32
+ mockResolveWorkspaceRoot.mockReturnValue('/test/workspace');
33
+
34
+ class FakeToolManager {
35
+ registerTool = vi.fn();
36
+ getTools = vi.fn(() => []);
37
+ }
38
+
39
+ class FakeTool {
40
+ toToolSchema() {
41
+ return {
42
+ type: 'function',
43
+ function: {
44
+ name: 'fake_tool',
45
+ },
46
+ };
47
+ }
48
+ }
49
+
50
+ class FakeAgent {
51
+ on = vi.fn();
52
+ off = vi.fn();
53
+ }
54
+
55
+ class FakeAppService {
56
+ async listContextMessages() {
57
+ return [];
58
+ }
59
+
60
+ async runForeground(
61
+ _request: unknown,
62
+ callbacks?: {
63
+ onContextUsage?: (usage: {
64
+ stepIndex: number;
65
+ messageCount: number;
66
+ contextTokens: number;
67
+ contextLimitTokens: number;
68
+ contextUsagePercent: number;
69
+ }) => void | Promise<void>;
70
+ onUsage?: (usage: {
71
+ sequence: number;
72
+ stepIndex: number;
73
+ messageId: string;
74
+ usage: {
75
+ prompt_tokens: number;
76
+ completion_tokens: number;
77
+ total_tokens: number;
78
+ };
79
+ cumulativeUsage: {
80
+ prompt_tokens: number;
81
+ completion_tokens: number;
82
+ total_tokens: number;
83
+ };
84
+ contextTokens?: number;
85
+ contextLimitTokens?: number;
86
+ contextUsagePercent?: number;
87
+ }) => void | Promise<void>;
88
+ }
89
+ ) {
90
+ await callbacks?.onContextUsage?.({
91
+ stepIndex: 1,
92
+ messageCount: 1,
93
+ contextTokens: 123,
94
+ contextLimitTokens: 1000,
95
+ contextUsagePercent: 12.3,
96
+ });
97
+
98
+ return {
99
+ executionId: 'exec_ctx',
100
+ conversationId: 'conv_ctx',
101
+ messages: [
102
+ {
103
+ messageId: 'msg_assistant',
104
+ role: 'assistant',
105
+ type: 'assistant-text',
106
+ content: 'done',
107
+ },
108
+ ],
109
+ finishReason: 'stop' as const,
110
+ steps: 1,
111
+ run: {},
112
+ };
113
+ }
114
+ }
115
+
116
+ mockGetSourceModules.mockResolvedValue({
117
+ ProviderRegistry: {
118
+ getModelIds: () => ['test-model'],
119
+ getModelConfig: () => ({
120
+ name: 'Test Model',
121
+ envApiKey: 'TEST_API_KEY',
122
+ model: 'test-model',
123
+ }),
124
+ createFromEnv: () => ({}),
125
+ },
126
+ loadEnvFiles: vi.fn().mockResolvedValue([]),
127
+ loadConfigToEnv: vi.fn().mockReturnValue([]),
128
+ createLoggerFromEnv: vi.fn(() => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn() })),
129
+ createAgentLoggerAdapter: vi.fn((logger: Record<string, unknown>) => ({
130
+ info: typeof logger.info === 'function' ? logger.info.bind(logger) : undefined,
131
+ warn: typeof logger.warn === 'function' ? logger.warn.bind(logger) : undefined,
132
+ error: typeof logger.error === 'function' ? logger.error.bind(logger) : undefined,
133
+ })),
134
+ StatelessAgent: FakeAgent,
135
+ AgentAppService: FakeAppService,
136
+ createSqliteAgentAppStore: () => ({
137
+ prepare: vi.fn().mockResolvedValue(undefined),
138
+ close: vi.fn().mockResolvedValue(undefined),
139
+ }),
140
+ DefaultToolManager: FakeToolManager,
141
+ BashTool: FakeTool,
142
+ WriteFileTool: FakeTool,
143
+ FileReadTool: FakeTool,
144
+ FileEditTool: FakeTool,
145
+ FileHistoryListTool: FakeTool,
146
+ FileHistoryRestoreTool: FakeTool,
147
+ GlobTool: FakeTool,
148
+ GrepTool: FakeTool,
149
+ SkillTool: FakeTool,
150
+ TaskTool: FakeTool,
151
+ TaskCreateTool: FakeTool,
152
+ TaskGetTool: FakeTool,
153
+ TaskListTool: FakeTool,
154
+ TaskUpdateTool: FakeTool,
155
+ TaskStopTool: FakeTool,
156
+ TaskOutputTool: FakeTool,
157
+ TaskStore: class {},
158
+ RealSubagentRunnerAdapter: class {},
159
+ } as unknown as Awaited<ReturnType<typeof sourceModules.getSourceModules>>);
160
+ });
161
+
162
+ afterEach(async () => {
163
+ await disposeAgentRuntime();
164
+ if (originalApiKey === undefined) {
165
+ delete process.env.TEST_API_KEY;
166
+ } else {
167
+ process.env.TEST_API_KEY = originalApiKey;
168
+ }
169
+ });
170
+
171
+ it('forwards realtime context usage to TUI handlers before final usage', async () => {
172
+ const onContextUsage = vi.fn();
173
+ const onUsage = vi.fn();
174
+ const handlers = {
175
+ onContextUsage,
176
+ onUsage,
177
+ } as AgentEventHandlers & {
178
+ onContextUsage: (event: AgentContextUsageEvent) => void;
179
+ };
180
+
181
+ await runAgentPrompt('show context', handlers);
182
+
183
+ expect(onContextUsage).toHaveBeenCalledTimes(1);
184
+ expect(onContextUsage).toHaveBeenCalledWith(
185
+ expect.objectContaining({
186
+ contextTokens: 123,
187
+ contextLimit: 1000,
188
+ contextUsagePercent: 12.3,
189
+ })
190
+ );
191
+ expect(onUsage).not.toHaveBeenCalled();
192
+ });
193
+ });
@@ -0,0 +1,236 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ vi.mock('./tool-confirmation', () => ({
4
+ resolveToolConfirmDecision: vi.fn(),
5
+ }));
6
+
7
+ vi.mock('./source-modules', () => ({
8
+ getSourceModules: vi.fn(),
9
+ resolveWorkspaceRoot: vi.fn(),
10
+ }));
11
+
12
+ vi.mock('../../../../src/agent/prompts/system', () => ({
13
+ buildSystemPrompt: vi.fn(() => 'Test system prompt'),
14
+ }));
15
+
16
+ import { disposeAgentRuntime, runAgentPrompt } from './runtime';
17
+ import * as sourceModules from './source-modules';
18
+
19
+ describe('runAgentPrompt error handling', () => {
20
+ const mockGetSourceModules = sourceModules.getSourceModules as ReturnType<typeof vi.fn>;
21
+ const mockResolveWorkspaceRoot = sourceModules.resolveWorkspaceRoot as ReturnType<typeof vi.fn>;
22
+ const originalApiKey = process.env.TEST_API_KEY;
23
+ const createLoggerFromEnv = vi.fn(() => ({
24
+ info: vi.fn(),
25
+ warn: vi.fn(),
26
+ error: vi.fn(),
27
+ close: vi.fn(),
28
+ }));
29
+ let buildModules: (
30
+ AppServiceClass: new () => {
31
+ listContextMessages: () => Promise<unknown[]>;
32
+ runForeground: (...args: any[]) => Promise<unknown>;
33
+ }
34
+ ) => Awaited<ReturnType<typeof sourceModules.getSourceModules>>;
35
+
36
+ beforeEach(() => {
37
+ vi.clearAllMocks();
38
+ process.env.TEST_API_KEY = 'test-key';
39
+ mockResolveWorkspaceRoot.mockReturnValue('/test/workspace');
40
+
41
+ class FakeToolManager {
42
+ registerTool = vi.fn();
43
+ getTools = vi.fn(() => []);
44
+ }
45
+
46
+ class FakeTool {
47
+ toToolSchema() {
48
+ return {
49
+ type: 'function',
50
+ function: {
51
+ name: 'fake_tool',
52
+ },
53
+ };
54
+ }
55
+ }
56
+
57
+ class FakeAgent {
58
+ on = vi.fn();
59
+ off = vi.fn();
60
+ }
61
+
62
+ class FakeAppService {
63
+ async listContextMessages() {
64
+ return [];
65
+ }
66
+
67
+ async runForeground(
68
+ _request: unknown,
69
+ callbacks?: {
70
+ onError?: (error: Error) => void | Promise<void>;
71
+ }
72
+ ) {
73
+ await callbacks?.onError?.(new Error('502 Bad Gateway: Upstream request failed'));
74
+
75
+ return {
76
+ executionId: 'exec_error',
77
+ conversationId: 'conv_error',
78
+ messages: [],
79
+ finishReason: 'error' as const,
80
+ steps: 1,
81
+ run: {},
82
+ };
83
+ }
84
+ }
85
+
86
+ buildModules = AppServiceClass =>
87
+ ({
88
+ ProviderRegistry: {
89
+ getModelIds: () => ['test-model'],
90
+ getModelConfig: () => ({
91
+ name: 'Test Model',
92
+ envApiKey: 'TEST_API_KEY',
93
+ model: 'test-model',
94
+ }),
95
+ createFromEnv: () => ({}),
96
+ },
97
+ loadEnvFiles: vi.fn().mockResolvedValue([]),
98
+ loadConfigToEnv: vi.fn().mockReturnValue([]),
99
+ createLoggerFromEnv,
100
+ createAgentLoggerAdapter: vi.fn(() => ({
101
+ info: vi.fn(),
102
+ warn: vi.fn(),
103
+ error: vi.fn(),
104
+ })),
105
+ StatelessAgent: FakeAgent,
106
+ AgentAppService: AppServiceClass,
107
+ createSqliteAgentAppStore: () => ({
108
+ prepare: vi.fn().mockResolvedValue(undefined),
109
+ close: vi.fn().mockResolvedValue(undefined),
110
+ }),
111
+ DefaultToolManager: FakeToolManager,
112
+ BashTool: FakeTool,
113
+ WriteFileTool: FakeTool,
114
+ FileReadTool: FakeTool,
115
+ FileEditTool: FakeTool,
116
+ FileHistoryListTool: FakeTool,
117
+ FileHistoryRestoreTool: FakeTool,
118
+ GlobTool: FakeTool,
119
+ GrepTool: FakeTool,
120
+ SkillTool: FakeTool,
121
+ TaskTool: FakeTool,
122
+ TaskCreateTool: FakeTool,
123
+ TaskGetTool: FakeTool,
124
+ TaskListTool: FakeTool,
125
+ TaskUpdateTool: FakeTool,
126
+ TaskStopTool: FakeTool,
127
+ TaskOutputTool: FakeTool,
128
+ TaskStore: class {},
129
+ RealSubagentRunnerAdapter: class {},
130
+ }) as unknown as Awaited<ReturnType<typeof sourceModules.getSourceModules>>;
131
+
132
+ mockGetSourceModules.mockResolvedValue(buildModules(FakeAppService));
133
+ });
134
+
135
+ afterEach(async () => {
136
+ await disposeAgentRuntime();
137
+ if (originalApiKey === undefined) {
138
+ delete process.env.TEST_API_KEY;
139
+ } else {
140
+ process.env.TEST_API_KEY = originalApiKey;
141
+ }
142
+ });
143
+
144
+ it('disables console logging for the TUI runtime', async () => {
145
+ await runAgentPrompt('hello', {});
146
+
147
+ expect(createLoggerFromEnv).toHaveBeenCalledWith(
148
+ expect.objectContaining({
149
+ AGENT_LOG_CONSOLE: 'false',
150
+ }),
151
+ '/test/workspace'
152
+ );
153
+ });
154
+
155
+ it('uses onError messages as the completion message when the run fails', async () => {
156
+ const result = await runAgentPrompt('hello', {});
157
+
158
+ expect(result.completionReason).toBe('error');
159
+ expect(result.completionMessage).toBe('502 Bad Gateway: Upstream request failed');
160
+ });
161
+
162
+ it('does not emit a terminal error stop for a retryable stream error that later succeeds', async () => {
163
+ class FakeAppServiceWithRetryableError {
164
+ async listContextMessages() {
165
+ return [];
166
+ }
167
+
168
+ async runForeground(
169
+ _request: unknown,
170
+ callbacks?: {
171
+ onError?: (error: Error) => void | Promise<void>;
172
+ onEvent?: (event: { eventType: string; data: Record<string, unknown> }) => Promise<void>;
173
+ }
174
+ ) {
175
+ await callbacks?.onError?.(new Error('Network connection lost. (chunk: gen-retryable)'));
176
+ await callbacks?.onEvent?.({
177
+ eventType: 'error',
178
+ data: {
179
+ message: 'Network connection lost. (chunk: gen-retryable)',
180
+ },
181
+ });
182
+ await callbacks?.onEvent?.({
183
+ eventType: 'chunk',
184
+ data: {
185
+ content: 'recovered response',
186
+ },
187
+ });
188
+ await callbacks?.onEvent?.({
189
+ eventType: 'done',
190
+ data: {
191
+ finishReason: 'stop',
192
+ },
193
+ });
194
+
195
+ return {
196
+ executionId: 'exec_retry_success',
197
+ conversationId: 'conv_retry_success',
198
+ messages: [],
199
+ finishReason: 'stop' as const,
200
+ steps: 2,
201
+ run: {},
202
+ };
203
+ }
204
+ }
205
+
206
+ mockGetSourceModules.mockResolvedValue(buildModules(FakeAppServiceWithRetryableError));
207
+
208
+ const handlers = {
209
+ onStop: vi.fn(),
210
+ onTextComplete: vi.fn(),
211
+ };
212
+
213
+ const result = await runAgentPrompt('hello', handlers);
214
+
215
+ expect(handlers.onStop).toHaveBeenCalledTimes(1);
216
+ expect(handlers.onStop).toHaveBeenCalledWith({ reason: 'stop' });
217
+ expect(handlers.onTextComplete).toHaveBeenCalledWith('recovered response');
218
+ expect(result.completionReason).toBe('stop');
219
+ expect(result.completionMessage).toBeUndefined();
220
+ expect(result.text).toBe('recovered response');
221
+ });
222
+
223
+ it('emits a terminal error stop after the run settles with finishReason=error', async () => {
224
+ const handlers = {
225
+ onStop: vi.fn(),
226
+ };
227
+
228
+ await runAgentPrompt('hello', handlers);
229
+
230
+ expect(handlers.onStop).toHaveBeenCalledTimes(1);
231
+ expect(handlers.onStop).toHaveBeenCalledWith({
232
+ reason: 'error',
233
+ message: undefined,
234
+ });
235
+ });
236
+ });
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ // 简单测试runtime模块的基础功能
4
+ describe('runtime module exports', () => {
5
+ it('should have expected exports', async () => {
6
+ // 动态导入以避免全局模拟问题
7
+ const runtimeModule = await import('./runtime');
8
+
9
+ expect(typeof runtimeModule.runAgentPrompt).toBe('function');
10
+ expect(typeof runtimeModule.getAgentModelLabel).toBe('function');
11
+ expect(typeof runtimeModule.getAgentModelId).toBe('function');
12
+ expect(typeof runtimeModule.listAgentModels).toBe('function');
13
+ expect(typeof runtimeModule.switchAgentModel).toBe('function');
14
+ expect(typeof runtimeModule.disposeAgentRuntime).toBe('function');
15
+ });
16
+ });