oricore 1.0.0

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 (221) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/dist/agent/agent/agentManager.d.ts +38 -0
  4. package/dist/agent/agent/builtin/common.d.ts +5 -0
  5. package/dist/agent/agent/builtin/explore.d.ts +5 -0
  6. package/dist/agent/agent/builtin/general-purpose.d.ts +5 -0
  7. package/dist/agent/agent/builtin/index.d.ts +5 -0
  8. package/dist/agent/agent/executor.d.ts +2 -0
  9. package/dist/agent/agent/types.d.ts +98 -0
  10. package/dist/api/engine.d.ts +213 -0
  11. package/dist/communication/index.d.ts +4 -0
  12. package/dist/communication/messageBus.d.ts +71 -0
  13. package/dist/core/at.d.ts +26 -0
  14. package/dist/core/backgroundTaskManager.d.ts +27 -0
  15. package/dist/core/compact.d.ts +9 -0
  16. package/dist/core/config.d.ts +103 -0
  17. package/dist/core/constants.d.ts +32 -0
  18. package/dist/core/context.d.ts +57 -0
  19. package/dist/core/globalData.d.ts +21 -0
  20. package/dist/core/history.d.ts +24 -0
  21. package/dist/core/ide.d.ts +103 -0
  22. package/dist/core/jsonl.d.ts +37 -0
  23. package/dist/core/llmsContext.d.ts +14 -0
  24. package/dist/core/loop.d.ts +82 -0
  25. package/dist/core/message.d.ts +132 -0
  26. package/dist/core/model.d.ts +79 -0
  27. package/dist/core/output-style/builtin/default.d.ts +2 -0
  28. package/dist/core/output-style/builtin/explanatory.d.ts +2 -0
  29. package/dist/core/output-style/builtin/index.d.ts +6 -0
  30. package/dist/core/output-style/builtin/miao.d.ts +2 -0
  31. package/dist/core/output-style/builtin/minimal.d.ts +2 -0
  32. package/dist/core/output-style/types.d.ts +6 -0
  33. package/dist/core/outputFormat.d.ts +29 -0
  34. package/dist/core/outputStyle.d.ts +43 -0
  35. package/dist/core/paths.d.ts +20 -0
  36. package/dist/core/planSystemPrompt.d.ts +5 -0
  37. package/dist/core/plugin.d.ts +138 -0
  38. package/dist/core/project.d.ts +64 -0
  39. package/dist/core/promptCache.d.ts +3 -0
  40. package/dist/core/query.d.ts +14 -0
  41. package/dist/core/rules.d.ts +8 -0
  42. package/dist/core/systemPrompt.d.ts +9 -0
  43. package/dist/core/thinking-config.d.ts +3 -0
  44. package/dist/core/usage.d.ts +14 -0
  45. package/dist/index.d.ts +16 -0
  46. package/dist/index.js +144432 -0
  47. package/dist/mcp/mcp.d.ts +49 -0
  48. package/dist/modes/builtin.d.ts +34 -0
  49. package/dist/modes/index.d.ts +8 -0
  50. package/dist/modes/registry.d.ts +18 -0
  51. package/dist/modes/types.d.ts +51 -0
  52. package/dist/platform/index.d.ts +5 -0
  53. package/dist/platform/node.d.ts +28 -0
  54. package/dist/platform/types.d.ts +41 -0
  55. package/dist/session/session.d.ts +43 -0
  56. package/dist/skill/skill.d.ts +79 -0
  57. package/dist/tools/tool.d.ts +119 -0
  58. package/dist/tools/tools/askUserQuestion.d.ts +48 -0
  59. package/dist/tools/tools/bash.d.ts +43 -0
  60. package/dist/tools/tools/edit.d.ts +9 -0
  61. package/dist/tools/tools/fetch.d.ts +9 -0
  62. package/dist/tools/tools/glob.d.ts +7 -0
  63. package/dist/tools/tools/grep.d.ts +22 -0
  64. package/dist/tools/tools/ls.d.ts +6 -0
  65. package/dist/tools/tools/read.d.ts +9 -0
  66. package/dist/tools/tools/skill.d.ts +7 -0
  67. package/dist/tools/tools/task.d.ts +14 -0
  68. package/dist/tools/tools/todo.d.ts +37 -0
  69. package/dist/tools/tools/write.d.ts +7 -0
  70. package/dist/utils/apiKeyRotation.d.ts +2 -0
  71. package/dist/utils/applyEdit.d.ts +17 -0
  72. package/dist/utils/background-detection.d.ts +2 -0
  73. package/dist/utils/dotenv.d.ts +9 -0
  74. package/dist/utils/env.d.ts +6 -0
  75. package/dist/utils/error.d.ts +11 -0
  76. package/dist/utils/execFileNoThrow.d.ts +8 -0
  77. package/dist/utils/files.d.ts +10 -0
  78. package/dist/utils/git.d.ts +163 -0
  79. package/dist/utils/ide.d.ts +27 -0
  80. package/dist/utils/ignore.d.ts +6 -0
  81. package/dist/utils/isLocal.d.ts +1 -0
  82. package/dist/utils/language.d.ts +9 -0
  83. package/dist/utils/list.d.ts +20 -0
  84. package/dist/utils/mergeSystemMessagesMiddleware.d.ts +2 -0
  85. package/dist/utils/messageNormalization.d.ts +22 -0
  86. package/dist/utils/path.d.ts +34 -0
  87. package/dist/utils/prependSystemMessageMiddleware.d.ts +2 -0
  88. package/dist/utils/project.d.ts +1 -0
  89. package/dist/utils/proxy.d.ts +18 -0
  90. package/dist/utils/randomUUID.d.ts +5 -0
  91. package/dist/utils/renderSessionMarkdown.d.ts +10 -0
  92. package/dist/utils/ripgrep.d.ts +16 -0
  93. package/dist/utils/safeFrontMatter.d.ts +11 -0
  94. package/dist/utils/safeParseJson.d.ts +1 -0
  95. package/dist/utils/safeStringify.d.ts +1 -0
  96. package/dist/utils/sanitizeAIResponse.d.ts +30 -0
  97. package/dist/utils/setTerminalTitle.d.ts +1 -0
  98. package/dist/utils/shell-execution.d.ts +44 -0
  99. package/dist/utils/string.d.ts +8 -0
  100. package/dist/utils/symbols.d.ts +14 -0
  101. package/dist/utils/system-encoding.d.ts +40 -0
  102. package/dist/utils/tokenCounter.d.ts +8 -0
  103. package/dist/utils/username.d.ts +1 -0
  104. package/package.json +106 -0
  105. package/src/agent/agent/agentManager.test.ts +124 -0
  106. package/src/agent/agent/agentManager.ts +372 -0
  107. package/src/agent/agent/builtin/common.ts +20 -0
  108. package/src/agent/agent/builtin/explore.ts +53 -0
  109. package/src/agent/agent/builtin/general-purpose.ts +38 -0
  110. package/src/agent/agent/builtin/index.ts +13 -0
  111. package/src/agent/agent/executor.test.ts +339 -0
  112. package/src/agent/agent/executor.ts +224 -0
  113. package/src/agent/agent/types.ts +119 -0
  114. package/src/api/engine.ts +466 -0
  115. package/src/communication/index.ts +18 -0
  116. package/src/communication/messageBus.ts +393 -0
  117. package/src/core/at.ts +315 -0
  118. package/src/core/backgroundTaskManager.ts +129 -0
  119. package/src/core/compact.ts +95 -0
  120. package/src/core/config.ts +441 -0
  121. package/src/core/constants.ts +82 -0
  122. package/src/core/context.ts +214 -0
  123. package/src/core/globalData.ts +77 -0
  124. package/src/core/history.ts +323 -0
  125. package/src/core/ide.ts +325 -0
  126. package/src/core/jsonl.ts +100 -0
  127. package/src/core/llmsContext.ts +117 -0
  128. package/src/core/loop.ts +638 -0
  129. package/src/core/message.ts +304 -0
  130. package/src/core/model.ts +2198 -0
  131. package/src/core/output-style/builtin/default.ts +9 -0
  132. package/src/core/output-style/builtin/explanatory.ts +22 -0
  133. package/src/core/output-style/builtin/index.ts +19 -0
  134. package/src/core/output-style/builtin/miao.ts +22 -0
  135. package/src/core/output-style/builtin/minimal.ts +8 -0
  136. package/src/core/output-style/types.ts +6 -0
  137. package/src/core/outputFormat.ts +93 -0
  138. package/src/core/outputStyle.ts +255 -0
  139. package/src/core/paths.ts +161 -0
  140. package/src/core/planSystemPrompt.ts +46 -0
  141. package/src/core/plugin.ts +299 -0
  142. package/src/core/project.ts +492 -0
  143. package/src/core/promptCache.ts +32 -0
  144. package/src/core/query.ts +46 -0
  145. package/src/core/rules.ts +56 -0
  146. package/src/core/systemPrompt.ts +176 -0
  147. package/src/core/thinking-config.ts +98 -0
  148. package/src/core/usage.ts +68 -0
  149. package/src/index.ts +39 -0
  150. package/src/mcp/mcp.ts +637 -0
  151. package/src/modes/builtin.ts +305 -0
  152. package/src/modes/index.ts +22 -0
  153. package/src/modes/registry.ts +39 -0
  154. package/src/modes/types.ts +56 -0
  155. package/src/platform/index.ts +6 -0
  156. package/src/platform/node.ts +108 -0
  157. package/src/platform/types.ts +54 -0
  158. package/src/plugins/index.ts +15 -0
  159. package/src/session/session.ts +187 -0
  160. package/src/skill/skill.ts +702 -0
  161. package/src/tools/tool.ts +378 -0
  162. package/src/tools/tools/askUserQuestion.ts +134 -0
  163. package/src/tools/tools/bash.test.ts +425 -0
  164. package/src/tools/tools/bash.ts +999 -0
  165. package/src/tools/tools/edit.ts +86 -0
  166. package/src/tools/tools/fetch.ts +129 -0
  167. package/src/tools/tools/glob.ts +69 -0
  168. package/src/tools/tools/grep.test.ts +194 -0
  169. package/src/tools/tools/grep.ts +358 -0
  170. package/src/tools/tools/ls.ts +51 -0
  171. package/src/tools/tools/read.test.ts +169 -0
  172. package/src/tools/tools/read.ts +284 -0
  173. package/src/tools/tools/skill.ts +73 -0
  174. package/src/tools/tools/task.test.ts +262 -0
  175. package/src/tools/tools/task.ts +284 -0
  176. package/src/tools/tools/todo.ts +269 -0
  177. package/src/tools/tools/write.ts +71 -0
  178. package/src/types.d.ts +18 -0
  179. package/src/utils/apiKeyRotation.test.ts +70 -0
  180. package/src/utils/apiKeyRotation.ts +24 -0
  181. package/src/utils/applyEdit.test.ts +388 -0
  182. package/src/utils/applyEdit.ts +547 -0
  183. package/src/utils/background-detection.test.ts +61 -0
  184. package/src/utils/background-detection.ts +58 -0
  185. package/src/utils/dotenv.ts +26 -0
  186. package/src/utils/env.ts +90 -0
  187. package/src/utils/error.ts +38 -0
  188. package/src/utils/execFileNoThrow.ts +49 -0
  189. package/src/utils/files.ts +93 -0
  190. package/src/utils/git.ts +1152 -0
  191. package/src/utils/ide.ts +279 -0
  192. package/src/utils/ignore.ts +275 -0
  193. package/src/utils/isLocal.ts +6 -0
  194. package/src/utils/language.ts +33 -0
  195. package/src/utils/list.ts +200 -0
  196. package/src/utils/mergeSystemMessagesMiddleware.ts +32 -0
  197. package/src/utils/messageNormalization.test.ts +401 -0
  198. package/src/utils/messageNormalization.ts +168 -0
  199. package/src/utils/path.ts +98 -0
  200. package/src/utils/prependSystemMessageMiddleware.ts +16 -0
  201. package/src/utils/project.ts +32 -0
  202. package/src/utils/proxy.ts +102 -0
  203. package/src/utils/randomUUID.ts +11 -0
  204. package/src/utils/renderSessionMarkdown.ts +175 -0
  205. package/src/utils/ripgrep.ts +189 -0
  206. package/src/utils/safeFrontMatter.test.ts +118 -0
  207. package/src/utils/safeFrontMatter.ts +68 -0
  208. package/src/utils/safeParseJson.ts +7 -0
  209. package/src/utils/safeStringify.ts +10 -0
  210. package/src/utils/sanitizeAIResponse.test.ts +135 -0
  211. package/src/utils/sanitizeAIResponse.ts +55 -0
  212. package/src/utils/setTerminalTitle.ts +7 -0
  213. package/src/utils/shell-execution.test.ts +237 -0
  214. package/src/utils/shell-execution.ts +279 -0
  215. package/src/utils/string.ts +13 -0
  216. package/src/utils/symbols.ts +18 -0
  217. package/src/utils/system-encoding.test.ts +164 -0
  218. package/src/utils/system-encoding.ts +296 -0
  219. package/src/utils/tokenCounter.test.ts +38 -0
  220. package/src/utils/tokenCounter.ts +19 -0
  221. package/src/utils/username.ts +21 -0
@@ -0,0 +1,38 @@
1
+ import { AGENT_TYPE } from '../../../core/constants';
2
+ import type { Context } from '../../../core/context';
3
+ import { type AgentDefinition, AgentSource } from '../types';
4
+ import { CONTEXT_NOTES, TASK_TOOL_NAME } from './common';
5
+
6
+ export function createGeneralPurposeAgent(opts: {
7
+ context: Context;
8
+ }): AgentDefinition {
9
+ const { context } = opts;
10
+
11
+ return {
12
+ agentType: AGENT_TYPE.GENERAL_PURPOSE,
13
+
14
+ whenToUse: `General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.`,
15
+
16
+ systemPrompt: `You are an AI coding assistant. Given the user's message, use the tools available to complete the task. Do what has been asked; nothing more, nothing less. When you complete the task simply respond with a detailed writeup.
17
+
18
+ Your strengths:
19
+ - Searching for code, configurations, and patterns across large codebases
20
+ - Analyzing multiple files to understand system architecture
21
+ - Investigating complex questions that require exploring many files
22
+ - Performing multi-step research tasks
23
+
24
+ Guidelines:
25
+ - For file searches: Use grep or glob when you need to search broadly. Use read when you know the specific file path.
26
+ - For analysis: Start broad and narrow down. Use multiple search strategies if the first doesn't yield results.
27
+ - Be thorough: Check multiple locations, consider different naming conventions, look for related files.
28
+ - NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one.
29
+ - NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested.
30
+
31
+ ${CONTEXT_NOTES}`,
32
+
33
+ model: context.config.model,
34
+ source: AgentSource.BuiltIn,
35
+ disallowedTools: [TASK_TOOL_NAME],
36
+ forkContext: false,
37
+ };
38
+ }
@@ -0,0 +1,13 @@
1
+ import type { Context } from '../../../core/context';
2
+ import type { AgentDefinition } from '../types';
3
+ import { createExploreAgent } from './explore';
4
+ import { createGeneralPurposeAgent } from './general-purpose';
5
+
6
+ export function getBuiltinAgents(opts: {
7
+ context: Context;
8
+ }): AgentDefinition[] {
9
+ return [
10
+ createExploreAgent(opts),
11
+ createGeneralPurposeAgent(opts),
12
+ ];
13
+ }
@@ -0,0 +1,339 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { Context } from '../../core/context';
3
+ import type { Tool } from '../../tools/tool';
4
+ import { executeAgent } from './executor';
5
+ import { type AgentDefinition, AgentSource } from './types';
6
+
7
+ /**
8
+ * Integration tests for executeAgent
9
+ *
10
+ * Note: Most comprehensive testing requires mocking the Project class,
11
+ * which has proven complex with Vitest. The core functionality is tested
12
+ * through:
13
+ * 1. The basic error handling test below
14
+ * 2. Manual testing in the development environment
15
+ * 3. End-to-end tests in the full application context
16
+ *
17
+ * Key behaviors tested manually:
18
+ * - onToolApprove callback propagation
19
+ * - Custom logPath usage
20
+ * - Model inheritance from context
21
+ * - Message metadata enhancement
22
+ * - Tool filtering
23
+ */
24
+
25
+ describe('executeAgent', () => {
26
+ test('should return error if agent has no available tools', async () => {
27
+ const context = await Context.create({
28
+ cwd: process.cwd(),
29
+ productName: 'test',
30
+ version: '1.0.0',
31
+ argvConfig: {},
32
+ plugins: [],
33
+ });
34
+
35
+ const definition: AgentDefinition = {
36
+ agentType: 'Test',
37
+ whenToUse: 'Test',
38
+ systemPrompt: 'Test',
39
+ model: 'test-model',
40
+ source: AgentSource.BuiltIn,
41
+ disallowedTools: ['read', 'write', 'glob', 'grep'],
42
+ };
43
+
44
+ const result = await executeAgent({
45
+ definition,
46
+ prompt: 'Test',
47
+ tools: [{ name: 'read' } as Tool, { name: 'write' } as Tool],
48
+ context,
49
+ cwd: '/test',
50
+ });
51
+
52
+ expect(result.status).toBe('failed');
53
+ expect(result.content).toContain('no available tools');
54
+
55
+ await context.destroy();
56
+ });
57
+
58
+ test('should throw error if no model is specified and definition model is missing', async () => {
59
+ const context = await Context.create({
60
+ cwd: process.cwd(),
61
+ productName: 'test',
62
+ version: '1.0.0',
63
+ argvConfig: { model: undefined },
64
+ plugins: [],
65
+ });
66
+
67
+ const definition: AgentDefinition = {
68
+ agentType: 'Test',
69
+ whenToUse: 'Test',
70
+ systemPrompt: 'Test system prompt',
71
+ model: '', // Empty model
72
+ source: AgentSource.BuiltIn,
73
+ tools: ['*'],
74
+ };
75
+
76
+ const result = await executeAgent({
77
+ definition,
78
+ prompt: 'Test prompt',
79
+ tools: [{ name: 'test-tool' } as Tool],
80
+ context,
81
+ cwd: '/test',
82
+ });
83
+
84
+ expect(result.status).toBe('failed');
85
+ expect(result.content).toContain('No model specified');
86
+
87
+ await context.destroy();
88
+ });
89
+
90
+ test('should throw error if agentType is missing', async () => {
91
+ const context = await Context.create({
92
+ cwd: process.cwd(),
93
+ productName: 'test',
94
+ version: '1.0.0',
95
+ argvConfig: {},
96
+ plugins: [],
97
+ });
98
+
99
+ const definition: AgentDefinition = {
100
+ agentType: '',
101
+ whenToUse: 'Test',
102
+ systemPrompt: 'Test system prompt',
103
+ model: 'test-model',
104
+ source: AgentSource.BuiltIn,
105
+ tools: ['*'],
106
+ };
107
+
108
+ const result = await executeAgent({
109
+ definition,
110
+ prompt: 'Test prompt',
111
+ tools: [{ name: 'test-tool' } as Tool],
112
+ context,
113
+ cwd: '/test',
114
+ });
115
+
116
+ expect(result.status).toBe('failed');
117
+ expect(result.content).toContain('must have agentType');
118
+
119
+ await context.destroy();
120
+ });
121
+
122
+ test('should throw error if systemPrompt is missing', async () => {
123
+ const context = await Context.create({
124
+ cwd: process.cwd(),
125
+ productName: 'test',
126
+ version: '1.0.0',
127
+ argvConfig: {},
128
+ plugins: [],
129
+ });
130
+
131
+ const definition: AgentDefinition = {
132
+ agentType: 'Test',
133
+ whenToUse: 'Test',
134
+ systemPrompt: '',
135
+ model: 'test-model',
136
+ source: AgentSource.BuiltIn,
137
+ tools: ['*'],
138
+ };
139
+
140
+ const result = await executeAgent({
141
+ definition,
142
+ prompt: 'Test prompt',
143
+ tools: [{ name: 'test-tool' } as Tool],
144
+ context,
145
+ cwd: '/test',
146
+ });
147
+
148
+ expect(result.status).toBe('failed');
149
+ expect(result.content).toContain('must have systemPrompt');
150
+
151
+ await context.destroy();
152
+ });
153
+
154
+ describe('resolveAgentModel', () => {
155
+ test('should use explicit model from options (priority 1)', async () => {
156
+ const context = await Context.create({
157
+ cwd: process.cwd(),
158
+ productName: 'test',
159
+ version: '1.0.0',
160
+ argvConfig: {
161
+ model: 'global-model',
162
+ agent: {
163
+ explore: { model: 'config-explore-model' },
164
+ },
165
+ },
166
+ plugins: [],
167
+ });
168
+
169
+ const definition: AgentDefinition = {
170
+ agentType: 'explore',
171
+ whenToUse: 'Test',
172
+ systemPrompt: 'Test system prompt',
173
+ model: 'definition-model',
174
+ source: AgentSource.BuiltIn,
175
+ tools: ['read'],
176
+ };
177
+
178
+ const result = await executeAgent({
179
+ definition,
180
+ prompt: 'Test prompt',
181
+ tools: [{ name: 'read' } as Tool],
182
+ context,
183
+ model: 'explicit-model', // This should take priority
184
+ cwd: '/test',
185
+ });
186
+
187
+ // The execution will fail because we don't have real tools set up,
188
+ // but we can verify the model was attempted to be used
189
+ expect(result.status).toBe('failed');
190
+
191
+ await context.destroy();
192
+ });
193
+
194
+ test('should use config agent model (priority 2)', async () => {
195
+ const context = await Context.create({
196
+ cwd: process.cwd(),
197
+ productName: 'test',
198
+ version: '1.0.0',
199
+ argvConfig: {
200
+ model: 'global-model',
201
+ agent: {
202
+ explore: { model: 'config-explore-model' },
203
+ },
204
+ },
205
+ plugins: [],
206
+ });
207
+
208
+ const definition: AgentDefinition = {
209
+ agentType: 'explore',
210
+ whenToUse: 'Test',
211
+ systemPrompt: 'Test system prompt',
212
+ model: 'definition-model',
213
+ source: AgentSource.BuiltIn,
214
+ tools: ['read'],
215
+ };
216
+
217
+ const result = await executeAgent({
218
+ definition,
219
+ prompt: 'Test prompt',
220
+ tools: [{ name: 'read' } as Tool],
221
+ context,
222
+ // No explicit model provided
223
+ cwd: '/test',
224
+ });
225
+
226
+ // config.agent.explore.model should be used
227
+ expect(result.status).toBe('failed');
228
+
229
+ await context.destroy();
230
+ });
231
+
232
+ test('should use agent definition model (priority 3)', async () => {
233
+ const context = await Context.create({
234
+ cwd: process.cwd(),
235
+ productName: 'test',
236
+ version: '1.0.0',
237
+ argvConfig: {
238
+ model: 'global-model',
239
+ // No agent config
240
+ },
241
+ plugins: [],
242
+ });
243
+
244
+ const definition: AgentDefinition = {
245
+ agentType: 'explore',
246
+ whenToUse: 'Test',
247
+ systemPrompt: 'Test system prompt',
248
+ model: 'definition-model',
249
+ source: AgentSource.BuiltIn,
250
+ tools: ['read'],
251
+ };
252
+
253
+ const result = await executeAgent({
254
+ definition,
255
+ prompt: 'Test prompt',
256
+ tools: [{ name: 'read' } as Tool],
257
+ context,
258
+ cwd: '/test',
259
+ });
260
+
261
+ // definition.model should be used
262
+ expect(result.status).toBe('failed');
263
+
264
+ await context.destroy();
265
+ });
266
+
267
+ test('should fallback to global model (priority 4)', async () => {
268
+ const context = await Context.create({
269
+ cwd: process.cwd(),
270
+ productName: 'test',
271
+ version: '1.0.0',
272
+ argvConfig: {
273
+ model: 'global-model',
274
+ },
275
+ plugins: [],
276
+ });
277
+
278
+ const definition: AgentDefinition = {
279
+ agentType: 'explore',
280
+ whenToUse: 'Test',
281
+ systemPrompt: 'Test system prompt',
282
+ model: '', // Empty model in definition
283
+ source: AgentSource.BuiltIn,
284
+ tools: ['read'],
285
+ };
286
+
287
+ const result = await executeAgent({
288
+ definition,
289
+ prompt: 'Test prompt',
290
+ tools: [{ name: 'read' } as Tool],
291
+ context,
292
+ cwd: '/test',
293
+ });
294
+
295
+ // global model should be used
296
+ expect(result.status).toBe('failed');
297
+
298
+ await context.destroy();
299
+ });
300
+
301
+ test('should handle MODEL_INHERIT correctly', async () => {
302
+ const context = await Context.create({
303
+ cwd: process.cwd(),
304
+ productName: 'test',
305
+ version: '1.0.0',
306
+ argvConfig: {
307
+ model: 'global-model',
308
+ agent: {
309
+ explore: { model: 'inherit' },
310
+ },
311
+ },
312
+ plugins: [],
313
+ });
314
+
315
+ const definition: AgentDefinition = {
316
+ agentType: 'explore',
317
+ whenToUse: 'Test',
318
+ systemPrompt: 'Test system prompt',
319
+ model: 'inherit',
320
+ source: AgentSource.BuiltIn,
321
+ tools: ['read'],
322
+ };
323
+
324
+ const result = await executeAgent({
325
+ definition,
326
+ prompt: 'Test prompt',
327
+ tools: [{ name: 'read' } as Tool],
328
+ context,
329
+ model: 'inherit',
330
+ cwd: '/test',
331
+ });
332
+
333
+ // Should skip 'inherit' values and fallback to global-model
334
+ expect(result.status).toBe('failed');
335
+
336
+ await context.destroy();
337
+ });
338
+ });
339
+ });
@@ -0,0 +1,224 @@
1
+ import type { Context } from '../../core/context';
2
+ import type { NormalizedMessage } from '../../core/message';
3
+ import { PluginHookType } from '../../core/plugin';
4
+ import { Project } from '../../core/project';
5
+ import { Session } from '../../session/session';
6
+ import type { Tool } from '../../tools/tool';
7
+ import type {
8
+ AgentDefinition,
9
+ AgentExecuteOptions,
10
+ AgentExecutionResult,
11
+ } from './types';
12
+
13
+ enum AgentStatus {
14
+ Completed = 'completed',
15
+ Failed = 'failed',
16
+ }
17
+
18
+ // Resolve model
19
+ const MODEL_INHERIT = 'inherit';
20
+
21
+ /**
22
+ * Resolve the model for an agent with the following priority:
23
+ * 1. Model explicitly passed in options
24
+ * 2. Model configured in config.agent.{agentType}.model
25
+ * 3. Model defined in agent definition
26
+ * 4. Global model from context.config.model (fallback)
27
+ */
28
+ function resolveAgentModel(
29
+ agentType: string,
30
+ options: AgentExecuteOptions,
31
+ definition: AgentDefinition,
32
+ context: Context,
33
+ ): string {
34
+ // Priority 1: Explicit model from options
35
+ if (options.model && options.model !== MODEL_INHERIT) {
36
+ return options.model;
37
+ }
38
+
39
+ // Priority 2: Config agent-specific model
40
+ const configModel = context.config.agent?.[agentType]?.model;
41
+ if (configModel && configModel !== MODEL_INHERIT) {
42
+ return configModel;
43
+ }
44
+
45
+ // Priority 3: Agent definition model
46
+ if (definition.model && definition.model !== MODEL_INHERIT) {
47
+ return definition.model;
48
+ }
49
+
50
+ // Priority 4: Global fallback
51
+ return context.config.model;
52
+ }
53
+
54
+ export async function executeAgent(
55
+ options: AgentExecuteOptions,
56
+ ): Promise<AgentExecutionResult> {
57
+ const {
58
+ definition,
59
+ prompt,
60
+ tools,
61
+ context,
62
+ signal,
63
+ onMessage,
64
+ onToolApprove,
65
+ resume,
66
+ } = options;
67
+
68
+ const startTime = Date.now();
69
+
70
+ const agentId = (() => {
71
+ if (resume) {
72
+ return resume;
73
+ }
74
+ return Session.createSessionId();
75
+ })();
76
+
77
+ try {
78
+ // Validate Agent definition
79
+ if (!definition.agentType) {
80
+ throw new Error('Agent definition must have agentType');
81
+ }
82
+ if (!definition.systemPrompt) {
83
+ throw new Error(`Agent '${definition.agentType}' must have systemPrompt`);
84
+ }
85
+
86
+ // Filter tools
87
+ const filteredToolList = filterTools(tools, definition);
88
+
89
+ if (filteredToolList.length === 0) {
90
+ throw new Error(
91
+ `Agent '${definition.agentType}' has no available tools after filtering.`,
92
+ );
93
+ }
94
+
95
+ // Resolve model using priority-based resolution
96
+ const modelName = resolveAgentModel(
97
+ definition.agentType,
98
+ options,
99
+ definition,
100
+ context,
101
+ );
102
+
103
+ if (!modelName) {
104
+ throw new Error(`No model specified for agent '${definition.agentType}'`);
105
+ }
106
+
107
+ // Create Project instance with agent log path
108
+ const project = new Project({
109
+ sessionId: `agent-${agentId}`,
110
+ parentSessionId: options.parentSessionId,
111
+ context,
112
+ });
113
+
114
+ // Execute using Project.send
115
+ const result = await project.sendWithSystemPromptAndTools(prompt, {
116
+ model: modelName,
117
+ systemPrompt: definition.systemPrompt,
118
+ tools: filteredToolList,
119
+ signal,
120
+ skipStopHook: true,
121
+ onMessage: async ({ message }) => {
122
+ // Add agent metadata
123
+ const enhancedMessage: NormalizedMessage = {
124
+ ...message,
125
+ metadata: {
126
+ ...(message.metadata || {}),
127
+ agentId,
128
+ agentType: definition.agentType,
129
+ },
130
+ };
131
+
132
+ if (onMessage) {
133
+ try {
134
+ await onMessage(enhancedMessage, agentId, modelName);
135
+ } catch (error) {
136
+ console.error('[executeAgent] Failed to send message:', error);
137
+ }
138
+ }
139
+ },
140
+ onToolApprove,
141
+ });
142
+
143
+ // Handle result
144
+ let executionResult: AgentExecutionResult;
145
+ if (result.success) {
146
+ executionResult = {
147
+ status: AgentStatus.Completed,
148
+ agentId,
149
+ content: extractFinalContent(result.data),
150
+ totalToolCalls: result.metadata?.toolCallsCount || 0,
151
+ totalDuration: Date.now() - startTime,
152
+ model: modelName,
153
+ usage: {
154
+ inputTokens: result.data.usage?.promptTokens || 0,
155
+ outputTokens: result.data.usage?.completionTokens || 0,
156
+ },
157
+ };
158
+ } else {
159
+ executionResult = {
160
+ status: AgentStatus.Failed,
161
+ agentId,
162
+ content: `Agent execution failed: ${result.error.message}`,
163
+ totalToolCalls: 0,
164
+ totalDuration: Date.now() - startTime,
165
+ model: modelName,
166
+ usage: { inputTokens: 0, outputTokens: 0 },
167
+ };
168
+ }
169
+
170
+ await context.apply({
171
+ hook: 'subagentStop',
172
+ args: [
173
+ {
174
+ parentSessionId: options.parentSessionId || '',
175
+ agentId,
176
+ agentType: definition.agentType,
177
+ result: executionResult,
178
+ usage: executionResult.usage,
179
+ totalToolCalls: executionResult.totalToolCalls,
180
+ totalDuration: executionResult.totalDuration,
181
+ model: modelName,
182
+ },
183
+ ],
184
+ type: PluginHookType.Series,
185
+ });
186
+
187
+ return executionResult;
188
+ } catch (error) {
189
+ return {
190
+ status: AgentStatus.Failed,
191
+ agentId,
192
+ content: `Agent execution error: ${error instanceof Error ? error.message : String(error)}`,
193
+ totalToolCalls: 0,
194
+ totalDuration: Date.now() - startTime,
195
+ usage: { inputTokens: 0, outputTokens: 0 },
196
+ };
197
+ }
198
+ }
199
+
200
+ function extractFinalContent(data: Record<string, unknown>): string {
201
+ if (data.text && typeof data.text === 'string') {
202
+ return data.text;
203
+ }
204
+ if (data.content && typeof data.content === 'string') {
205
+ return data.content;
206
+ }
207
+ return 'Agent completed successfully';
208
+ }
209
+
210
+ function filterTools(allTools: Tool[], agentDef: AgentDefinition): Tool[] {
211
+ const { tools, disallowedTools } = agentDef;
212
+ const disallowedSet = new Set(disallowedTools || []);
213
+ const hasWildcard =
214
+ tools === undefined || (tools.length === 1 && tools[0] === '*');
215
+
216
+ if (hasWildcard) {
217
+ return allTools.filter((tool) => !disallowedSet.has(tool.name));
218
+ }
219
+
220
+ const allowedSet = new Set(tools);
221
+ return allTools.filter(
222
+ (tool) => allowedSet.has(tool.name) && !disallowedSet.has(tool.name),
223
+ );
224
+ }