micro-models-agent 0.2.50 → 0.2.52

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 (150) hide show
  1. package/dist/agent/loop.d.ts.map +1 -1
  2. package/dist/agent/loop.js +87 -303
  3. package/dist/agent/loop.js.map +1 -1
  4. package/dist/agent/modes/applier.d.ts +43 -0
  5. package/dist/agent/modes/applier.d.ts.map +1 -0
  6. package/dist/agent/modes/applier.js +30 -0
  7. package/dist/agent/modes/applier.js.map +1 -0
  8. package/dist/agent/modes/registry.d.ts +35 -0
  9. package/dist/agent/modes/registry.d.ts.map +1 -0
  10. package/dist/agent/modes/registry.js +169 -0
  11. package/dist/agent/modes/registry.js.map +1 -0
  12. package/dist/agent/modes/types.d.ts +54 -0
  13. package/dist/agent/modes/types.d.ts.map +1 -0
  14. package/dist/agent/modes/types.js +13 -0
  15. package/dist/agent/modes/types.js.map +1 -0
  16. package/dist/agent/react-loop.d.ts +61 -0
  17. package/dist/agent/react-loop.d.ts.map +1 -0
  18. package/dist/agent/react-loop.js +119 -0
  19. package/dist/agent/react-loop.js.map +1 -0
  20. package/dist/agent/tool-executor.d.ts +44 -0
  21. package/dist/agent/tool-executor.d.ts.map +1 -0
  22. package/dist/agent/tool-executor.js +186 -0
  23. package/dist/agent/tool-executor.js.map +1 -0
  24. package/dist/config/types.d.ts +5 -2
  25. package/dist/config/types.d.ts.map +1 -1
  26. package/dist/plugins/adapters/hooks-adapter.d.ts +12 -0
  27. package/dist/plugins/adapters/hooks-adapter.d.ts.map +1 -0
  28. package/dist/plugins/adapters/hooks-adapter.js +23 -0
  29. package/dist/plugins/adapters/hooks-adapter.js.map +1 -0
  30. package/dist/plugins/loader.d.ts +13 -0
  31. package/dist/plugins/loader.d.ts.map +1 -0
  32. package/dist/plugins/loader.js +145 -0
  33. package/dist/plugins/loader.js.map +1 -0
  34. package/dist/plugins/manager.d.ts +52 -0
  35. package/dist/plugins/manager.d.ts.map +1 -0
  36. package/dist/plugins/manager.js +183 -0
  37. package/dist/plugins/manager.js.map +1 -0
  38. package/dist/plugins/types.d.ts +73 -0
  39. package/dist/plugins/types.d.ts.map +1 -0
  40. package/dist/plugins/types.js +8 -0
  41. package/dist/plugins/types.js.map +1 -0
  42. package/dist/repl/commands.d.ts +1 -0
  43. package/dist/repl/commands.d.ts.map +1 -1
  44. package/dist/repl/commands.js.map +1 -1
  45. package/dist/repl/index.d.ts.map +1 -1
  46. package/dist/repl/index.js +3 -2
  47. package/dist/repl/index.js.map +1 -1
  48. package/dist/session/store.d.ts +4 -2
  49. package/dist/session/store.d.ts.map +1 -1
  50. package/dist/session/store.js +4 -2
  51. package/dist/session/store.js.map +1 -1
  52. package/dist/tools/bash.d.ts +4 -0
  53. package/dist/tools/bash.d.ts.map +1 -1
  54. package/dist/tools/bash.js +24 -0
  55. package/dist/tools/bash.js.map +1 -1
  56. package/dist/tools/fs/copy-file.d.ts +2 -0
  57. package/dist/tools/fs/copy-file.d.ts.map +1 -1
  58. package/dist/tools/fs/copy-file.js +7 -0
  59. package/dist/tools/fs/copy-file.js.map +1 -1
  60. package/dist/tools/fs/create-dir.d.ts +2 -0
  61. package/dist/tools/fs/create-dir.d.ts.map +1 -1
  62. package/dist/tools/fs/create-dir.js +7 -0
  63. package/dist/tools/fs/create-dir.js.map +1 -1
  64. package/dist/tools/fs/delete-file.d.ts +2 -0
  65. package/dist/tools/fs/delete-file.d.ts.map +1 -1
  66. package/dist/tools/fs/delete-file.js +7 -0
  67. package/dist/tools/fs/delete-file.js.map +1 -1
  68. package/dist/tools/fs/edit-file.d.ts +2 -0
  69. package/dist/tools/fs/edit-file.d.ts.map +1 -1
  70. package/dist/tools/fs/edit-file.js +7 -0
  71. package/dist/tools/fs/edit-file.js.map +1 -1
  72. package/dist/tools/fs/file-info.d.ts +2 -0
  73. package/dist/tools/fs/file-info.d.ts.map +1 -1
  74. package/dist/tools/fs/file-info.js +7 -0
  75. package/dist/tools/fs/file-info.js.map +1 -1
  76. package/dist/tools/fs/glob-search.d.ts +2 -0
  77. package/dist/tools/fs/glob-search.d.ts.map +1 -1
  78. package/dist/tools/fs/glob-search.js +7 -0
  79. package/dist/tools/fs/glob-search.js.map +1 -1
  80. package/dist/tools/fs/grep-search.d.ts +2 -0
  81. package/dist/tools/fs/grep-search.d.ts.map +1 -1
  82. package/dist/tools/fs/grep-search.js +7 -0
  83. package/dist/tools/fs/grep-search.js.map +1 -1
  84. package/dist/tools/fs/list-dir.d.ts +2 -0
  85. package/dist/tools/fs/list-dir.d.ts.map +1 -1
  86. package/dist/tools/fs/list-dir.js +7 -0
  87. package/dist/tools/fs/list-dir.js.map +1 -1
  88. package/dist/tools/fs/move-file.d.ts +2 -0
  89. package/dist/tools/fs/move-file.d.ts.map +1 -1
  90. package/dist/tools/fs/move-file.js +7 -0
  91. package/dist/tools/fs/move-file.js.map +1 -1
  92. package/dist/tools/fs/read-file.d.ts +2 -0
  93. package/dist/tools/fs/read-file.d.ts.map +1 -1
  94. package/dist/tools/fs/read-file.js +7 -0
  95. package/dist/tools/fs/read-file.js.map +1 -1
  96. package/dist/tools/fs/tree.d.ts +2 -0
  97. package/dist/tools/fs/tree.d.ts.map +1 -1
  98. package/dist/tools/fs/tree.js +7 -0
  99. package/dist/tools/fs/tree.js.map +1 -1
  100. package/dist/tools/fs/write-file.d.ts +2 -0
  101. package/dist/tools/fs/write-file.d.ts.map +1 -1
  102. package/dist/tools/fs/write-file.js +7 -0
  103. package/dist/tools/fs/write-file.js.map +1 -1
  104. package/dist/tools/native/approve.d.ts +11 -0
  105. package/dist/tools/native/approve.d.ts.map +1 -0
  106. package/dist/tools/native/approve.js +39 -0
  107. package/dist/tools/native/approve.js.map +1 -0
  108. package/dist/tools/native/plan.d.ts +10 -0
  109. package/dist/tools/native/plan.d.ts.map +1 -0
  110. package/dist/tools/native/plan.js +46 -0
  111. package/dist/tools/native/plan.js.map +1 -0
  112. package/dist/tools/native/question.d.ts +11 -0
  113. package/dist/tools/native/question.d.ts.map +1 -0
  114. package/dist/tools/native/question.js +41 -0
  115. package/dist/tools/native/question.js.map +1 -0
  116. package/dist/tools/native/theme.d.ts +10 -0
  117. package/dist/tools/native/theme.d.ts.map +1 -0
  118. package/dist/tools/native/theme.js +106 -0
  119. package/dist/tools/native/theme.js.map +1 -0
  120. package/dist/tools/native/todo.d.ts +10 -0
  121. package/dist/tools/native/todo.d.ts.map +1 -0
  122. package/dist/tools/native/todo.js +58 -0
  123. package/dist/tools/native/todo.js.map +1 -0
  124. package/dist/tools/native/user-profile.d.ts +10 -0
  125. package/dist/tools/native/user-profile.d.ts.map +1 -0
  126. package/dist/tools/native/user-profile.js +58 -0
  127. package/dist/tools/native/user-profile.js.map +1 -0
  128. package/dist/tools/registry.d.ts +11 -10
  129. package/dist/tools/registry.d.ts.map +1 -1
  130. package/dist/tools/registry.js +40 -147
  131. package/dist/tools/registry.js.map +1 -1
  132. package/dist/tools/subagent.d.ts +2 -1
  133. package/dist/tools/subagent.d.ts.map +1 -1
  134. package/dist/tools/subagent.js +7 -4
  135. package/dist/tools/subagent.js.map +1 -1
  136. package/dist/tools/types.d.ts +59 -0
  137. package/dist/tools/types.d.ts.map +1 -0
  138. package/dist/tools/types.js +2 -0
  139. package/dist/tools/types.js.map +1 -0
  140. package/dist/tools/web-fetch.d.ts +2 -0
  141. package/dist/tools/web-fetch.d.ts.map +1 -1
  142. package/dist/tools/web-fetch.js +7 -0
  143. package/dist/tools/web-fetch.js.map +1 -1
  144. package/dist/tools/web-search.d.ts +2 -0
  145. package/dist/tools/web-search.d.ts.map +1 -1
  146. package/dist/tools/web-search.js +7 -0
  147. package/dist/tools/web-search.js.map +1 -1
  148. package/dist/updater.js +1 -1
  149. package/dist/updater.js.map +1 -1
  150. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAc9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAmFpD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,MAAM,EACd,cAAc,CAAC,EAAE,cAAc,EAC/B,gBAAgB,CAAC,EAAE,WAAW,EAAE,EAChC,QAAQ,CAAC,EAAE,cAAc,EACzB,YAAY,CAAC,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,GACtE,OAAO,CAAC,eAAe,CAAC,CAq5B1B"}
1
+ {"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAc9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAyGpD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,MAAM,EACd,cAAc,CAAC,EAAE,cAAc,EAC/B,gBAAgB,CAAC,EAAE,WAAW,EAAE,EAChC,QAAQ,CAAC,EAAE,cAAc,EACzB,YAAY,CAAC,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,GACtE,OAAO,CAAC,eAAe,CAAC,CAysB1B"}
@@ -4,22 +4,42 @@ import { jsonrepair } from 'jsonrepair';
4
4
  import { parseFileRefs } from '../file-loader.js';
5
5
  import { ContextManager } from './context.js';
6
6
  import { autoLearnFromError } from './memory.js';
7
- import { appendNote, collectCodingStyle, loadProfile } from './user-profile.js';
7
+ import { collectCodingStyle } from './user-profile.js';
8
+ import { ModeRegistry } from './modes/registry.js';
9
+ import { applyMode } from './modes/applier.js';
8
10
  import { loadSkill, loadSkillsFromRefs, enforceSkillBudget } from '../skills/loader.js';
9
11
  import { runPreToolUseHooks, runPostToolUseHooks } from '../tools/hooks.js';
10
12
  import { isMCPCommand, parseMCPCommand, callMCPTool } from '../tools/mcp.js';
11
- import { createPlan, updatePlanStep, showPlan, clearPlan, parsePlanCommand, getPlanPendingCount } from '../tools/plan.js';
12
- import { addTodo, doneTodo, removeTodo, listTodos, parseTodoCommand } from '../tools/todo.js';
13
- import { parseQuestionCommand } from '../tools/question.js';
14
- import { parseApproveCommand } from '../tools/approve.js';
13
+ import { showPlan, getPlanPendingCount } from '../tools/plan.js';
14
+ import { listTodos } from '../tools/todo.js';
15
15
  import { checkFileSyntax } from '../tools/syntax-check.js';
16
- import { listThemes, switchTheme } from '../styling/index.js';
17
- import { loadThemeConfig, installTheme, removeTheme } from '../styling/loader.js';
18
- import { loadConfig, saveConfig } from '../config/config.js';
16
+ import { loadConfig } from '../config/config.js';
19
17
  import { buildSystemPrompt } from './system-prompt.js';
20
18
  import { logInfo, logWarn, logError, logDebug, logToolCall, setStatusWriter, writeStatus } from '../logger.js';
21
19
  import { buildSubagentContext, runIsolatedLoop, extractAllToolCalls, validateToolCall, doChat } from '../tools/subagent.js';
22
20
  import { SubagentRenderer } from '../ui/subagent-renderer.js';
21
+ import { ToolExecutor } from './tool-executor.js';
22
+ import { planHandler } from '../tools/native/plan.js';
23
+ import { todoHandler } from '../tools/native/todo.js';
24
+ import { questionHandler } from '../tools/native/question.js';
25
+ import { approveHandler } from '../tools/native/approve.js';
26
+ import { userProfileHandler } from '../tools/native/user-profile.js';
27
+ import { themeHandler } from '../tools/native/theme.js';
28
+ import { bashHandler } from '../tools/bash.js';
29
+ import { readFileHandler } from '../tools/fs/read-file.js';
30
+ import { writeFileHandler } from '../tools/fs/write-file.js';
31
+ import { editFileHandler } from '../tools/fs/edit-file.js';
32
+ import { listDirHandler } from '../tools/fs/list-dir.js';
33
+ import { createDirHandler } from '../tools/fs/create-dir.js';
34
+ import { deleteFileHandler } from '../tools/fs/delete-file.js';
35
+ import { copyFileHandler } from '../tools/fs/copy-file.js';
36
+ import { moveFileHandler } from '../tools/fs/move-file.js';
37
+ import { fileInfoHandler } from '../tools/fs/file-info.js';
38
+ import { globSearchHandler } from '../tools/fs/glob-search.js';
39
+ import { grepSearchHandler } from '../tools/fs/grep-search.js';
40
+ import { treeHandler } from '../tools/fs/tree.js';
41
+ import { webSearchHandler } from '../tools/web-search.js';
42
+ import { webFetchHandler } from '../tools/web-fetch.js';
23
43
  /**
24
44
  * Умная обрезка tool-результатов для экономии токенов в контексте.
25
45
  *
@@ -123,6 +143,10 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
123
143
  const { prompt: enrichedPrompt, files } = await parseFileRefs(prompt);
124
144
  logDebug('LOOP', `parseFileRefs: ${files.length} files, prompt len: ${enrichedPrompt.length}`);
125
145
  const quiet = options?.quiet ?? false;
146
+ // ─── ModeRegistry: конфигурация режима ──
147
+ const modeRegistry = new ModeRegistry();
148
+ const modeConfig = modeRegistry.getOrFallback(options?.mode || 'base');
149
+ const applied = applyMode(modeConfig, { quiet, showThinking: options?.showThinking });
126
150
  // Автозагрузка скилов из промпта (если пользователь указал @skill-name)
127
151
  const loadedSkills = loadSkillsFromRefs(prompt, process.cwd());
128
152
  const loadedSkillNames = new Set(loadedSkills.map(s => s.name));
@@ -130,17 +154,20 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
130
154
  logInfo('LOOP', `loaded ${loadedSkills.length} skills: ${loadedSkills.map(s => s.name).join(', ')}`);
131
155
  renderer?.writeInfo(` Loaded ${loadedSkills.length} skill(s): ${loadedSkills.map(s => s.name).join(', ')}`);
132
156
  }
133
- if (options?.mode === 'pro' && !loadedSkillNames.has('grill-me')) {
134
- const grill = loadSkill('grill-me', process.cwd());
135
- if (grill) {
136
- loadedSkills.push(grill);
137
- loadedSkillNames.add('grill-me');
138
- logInfo('LOOP', 'auto-loaded grill-me skill for pro mode');
139
- renderer?.writeInfo(' Auto-loaded @grill-me skill for pro mode');
140
- }
141
- else {
142
- logWarn('LOOP', 'grill-me skill not found for pro mode');
143
- renderer?.writeWarning(' Warning: @grill-me skill not found (required for pro mode)');
157
+ // Принудительная загрузка скилов из ModeConfig
158
+ for (const skillName of applied.forcedSkills) {
159
+ if (!loadedSkillNames.has(skillName)) {
160
+ const skill = loadSkill(skillName, process.cwd());
161
+ if (skill) {
162
+ loadedSkills.push(skill);
163
+ loadedSkillNames.add(skillName);
164
+ logInfo('LOOP', `auto-loaded ${skillName} skill for ${modeConfig.id} mode`);
165
+ renderer?.writeInfo(` Auto-loaded @${skillName} skill for ${modeConfig.id} mode`);
166
+ }
167
+ else {
168
+ logWarn('LOOP', `${skillName} skill not found for ${modeConfig.id} mode`);
169
+ renderer?.writeWarning(` Warning: @${skillName} skill not found (required for ${modeConfig.id} mode)`);
170
+ }
144
171
  }
145
172
  }
146
173
  // Ограничение количества скилов в base mode
@@ -149,10 +176,10 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
149
176
  ? (config.agent.maxSkillsPro ?? 0)
150
177
  : (config.agent.maxSkillsBase ?? 2);
151
178
  if (maxSkills > 0 && loadedSkills.length > maxSkills) {
152
- const budget = config.agent.skillTokenBudget ?? 4000;
179
+ const budget = applied.skillTokenBudget;
153
180
  const { allowed, dropped } = enforceSkillBudget(loadedSkills, budget);
154
181
  if (dropped.length > 0) {
155
- renderer?.writeWarning(` Skill limit (${maxSkills} max in ${options?.mode || 'base'} mode): dropped ${dropped.join(', ')}`);
182
+ renderer?.writeWarning(` Skill limit (${maxSkills} max in ${modeConfig.id} mode): dropped ${dropped.join(', ')}`);
156
183
  logInfo('LOOP', `skill limit: dropped ${dropped.join(', ')} (budget: ${budget} tokens)`);
157
184
  loadedSkills.length = 0;
158
185
  loadedSkills.push(...allowed);
@@ -187,6 +214,16 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
187
214
  let aborted = false;
188
215
  let continuationNudges = 0; // счётчик подсказок "продолжай работать"
189
216
  let directWriteStreak = 0; // количество прямых write_file без subagent
217
+ // ─── ToolExecutor: единый диспетчер инструментов ──
218
+ const toolExecutor = new ToolExecutor();
219
+ toolExecutor.registerAll([
220
+ planHandler, todoHandler, questionHandler, approveHandler,
221
+ userProfileHandler, themeHandler, bashHandler,
222
+ readFileHandler, writeFileHandler, editFileHandler, listDirHandler,
223
+ createDirHandler, deleteFileHandler, copyFileHandler, moveFileHandler,
224
+ fileInfoHandler, globSearchHandler, grepSearchHandler, treeHandler,
225
+ webSearchHandler, webFetchHandler,
226
+ ]);
190
227
  // Reset abort signal for this run
191
228
  if (renderer?.abortSignal === undefined) {
192
229
  // Renderer doesn't support abort — fine
@@ -539,191 +576,8 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
539
576
  let toolOutput = '';
540
577
  let toolExitCode = 0;
541
578
  const toolStart = performance.now();
542
- // ─── Нативные инструменты (plan/todo/question/approve) ──
543
- if (parsed.name === 'plan') {
544
- const planName = String(parsed.arguments.TaskName || parsed.arguments.name || parsed.arguments.plan || 'Untitled');
545
- const stepsRaw = parsed.arguments.Steps || parsed.arguments.steps || [];
546
- const steps = Array.isArray(stepsRaw) ? stepsRaw : String(stepsRaw).split('\n').filter((s) => s.trim());
547
- if (!quiet) {
548
- renderer?.writeBracketTag('plan', 'create');
549
- }
550
- toolOutput = createPlan(planName, steps);
551
- // If plan has 3+ steps, suggest using subagent for isolation
552
- if (steps.length >= 3) {
553
- toolOutput += `\n\nIMPORTANT: Plan has ${steps.length} steps. Use subagent tool for EACH step to work in isolation:\n`;
554
- for (let i = 0; i < steps.length; i++) {
555
- toolOutput += `subagent("Step ${i + 1}: ${steps[i]}. List existing files and packages.")\n`;
556
- }
557
- toolOutput += `This prevents context loss. Do NOT use write_file directly — delegate to subagents.`;
558
- }
559
- if (!quiet && toolOutput) {
560
- renderer?.writeLine(toolOutput);
561
- }
562
- }
563
- else if (parsed.name === 'todo') {
564
- const action = String(parsed.arguments.action || 'list');
565
- const text = String(parsed.arguments.text || '');
566
- if (!quiet) {
567
- renderer?.writeBracketTag('todo', action);
568
- }
569
- switch (action) {
570
- case 'add':
571
- toolOutput = addTodo(text);
572
- break;
573
- case 'done':
574
- toolOutput = doneTodo(text);
575
- break;
576
- case 'remove':
577
- case 'rm':
578
- toolOutput = removeTodo(text);
579
- break;
580
- case 'list':
581
- case 'ls':
582
- toolOutput = listTodos();
583
- break;
584
- default:
585
- toolOutput = 'Unknown todo action. Use: add, done, remove, list';
586
- toolExitCode = 1;
587
- }
588
- if (!quiet && toolExitCode === 0 && toolOutput) {
589
- renderer?.writeLine(toolOutput);
590
- }
591
- }
592
- else if (parsed.name === 'question') {
593
- const question = String(parsed.arguments.question || parsed.arguments.text || '');
594
- const choicesArr = Array.isArray(parsed.arguments.choices) ? parsed.arguments.choices : [];
595
- if (!quiet) {
596
- renderer?.writeBracketTag('question', question);
597
- }
598
- messages.push({
599
- role: 'tool',
600
- content: `Question asked: ${question}`,
601
- tool_call_id: toolId,
602
- });
603
- return {
604
- messages,
605
- questionPending: { question, choices: choicesArr.length > 0 ? choicesArr : undefined },
606
- };
607
- }
608
- else if (parsed.name === 'approve') {
609
- const action = String(parsed.arguments.action || parsed.arguments.text || '');
610
- if (!quiet) {
611
- renderer?.writeBracketTag('approve', action);
612
- }
613
- messages.push({
614
- role: 'tool',
615
- content: `Approval requested: ${action}`,
616
- tool_call_id: toolId,
617
- });
618
- return {
619
- messages,
620
- questionPending: { question: `Approve: ${action}?`, choices: ['Yes', 'No'] },
621
- };
622
- }
623
- else if (parsed.name === 'user_profile') {
624
- const action = String(parsed.arguments.action || 'show');
625
- if (!quiet) {
626
- renderer?.writeBracketTag('user_profile', action);
627
- }
628
- switch (action) {
629
- case 'show': {
630
- const content = loadProfile();
631
- toolOutput = content || 'User profile is empty.';
632
- break;
633
- }
634
- case 'add_note': {
635
- const note = String(parsed.arguments.note || parsed.arguments.text || '');
636
- const section = String(parsed.arguments.section || 'Notes');
637
- if (!note) {
638
- toolOutput = 'Usage: user_profile action=add_note section=Notes note=...';
639
- toolExitCode = 1;
640
- break;
641
- }
642
- appendNote(note, section);
643
- toolOutput = `Note added to "${section}" section.`;
644
- break;
645
- }
646
- default:
647
- toolOutput = 'Unknown user_profile action. Use: show, add_note';
648
- toolExitCode = 1;
649
- }
650
- }
651
- else if (parsed.name === 'theme') {
652
- const action = String(parsed.arguments.action || 'list');
653
- const name = String(parsed.arguments.name || '');
654
- const url = String(parsed.arguments.url || '');
655
- if (!quiet) {
656
- renderer?.writeBracketTag('theme', action);
657
- }
658
- switch (action) {
659
- case 'list': {
660
- const allThemes = listThemes();
661
- const current = loadThemeConfig().name;
662
- toolOutput = `Available themes (${allThemes.length}):\n`;
663
- for (const t of allThemes) {
664
- toolOutput += ` ${t}${t === current ? ' (active)' : ''}\n`;
665
- }
666
- break;
667
- }
668
- case 'show': {
669
- const config = loadThemeConfig();
670
- toolOutput = `Theme: ${config.name}\nColors: ${JSON.stringify(config.colors, null, 2)}\nIcons: ${JSON.stringify(config.icons, null, 2)}`;
671
- break;
672
- }
673
- case 'switch': {
674
- const allThemes = listThemes();
675
- if (!allThemes.includes(name)) {
676
- toolOutput = `Unknown theme "${name}". Available: ${allThemes.join(', ')}`;
677
- toolExitCode = 1;
678
- break;
679
- }
680
- const appConfig = loadConfig();
681
- appConfig.theme = name;
682
- saveConfig(appConfig);
683
- switchTheme(name);
684
- try {
685
- const { resetInkTheme } = await import('../ui/ink/theme.js');
686
- resetInkTheme();
687
- }
688
- catch { /* not in ink mode */ }
689
- toolOutput = `Theme switched to "${name}"`;
690
- break;
691
- }
692
- case 'install': {
693
- if (!name || !url) {
694
- toolOutput = 'Usage: theme install <name> <url>';
695
- toolExitCode = 1;
696
- break;
697
- }
698
- try {
699
- const resp = await fetch(url);
700
- const text = await resp.text();
701
- JSON.parse(text);
702
- installTheme(name, text);
703
- toolOutput = `Theme "${name}" installed from ${url}`;
704
- }
705
- catch (e) {
706
- toolOutput = `Failed to install theme: ${e.message}`;
707
- toolExitCode = 1;
708
- }
709
- break;
710
- }
711
- case 'remove': {
712
- if (!removeTheme(name)) {
713
- toolOutput = `Theme "${name}" not found`;
714
- toolExitCode = 1;
715
- break;
716
- }
717
- toolOutput = `Theme "${name}" removed`;
718
- break;
719
- }
720
- default:
721
- toolOutput = 'Unknown theme action. Use: list, show, switch, install, remove';
722
- toolExitCode = 1;
723
- }
724
- }
725
- else if (parsed.name === 'subagent') {
726
- // ─── Субагент: изолированный контекст для задачи ──
579
+ // ─── Субагент: изолированный контекст для задачи ──
580
+ if (parsed.name === 'subagent') {
727
581
  const task = String(parsed.arguments.task || '');
728
582
  if (!task) {
729
583
  toolOutput = 'Error: task is required';
@@ -732,10 +586,9 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
732
586
  else {
733
587
  const autoContext = buildSubagentContext(process.cwd());
734
588
  logInfo('AGENT', `spawning subagent | task: "${task.slice(0, 150)}"`);
735
- // Создаём буферизированный рендерер для субагента
736
589
  const subRenderer = new SubagentRenderer();
737
590
  try {
738
- const childResult = await runIsolatedLoop(provider, registry, task, autoContext, process.cwd(), quiet, subRenderer);
591
+ const childResult = await runIsolatedLoop(provider, registry, task, autoContext, process.cwd(), quiet, subRenderer, toolExecutor);
739
592
  toolOutput = childResult.summary;
740
593
  logInfo('AGENT', `subagent returned | files: ${childResult.createdFiles.length} | turns: ${childResult.turns} | errors: ${childResult.errors.length}`);
741
594
  if (childResult.createdFiles.length > 0) {
@@ -744,7 +597,6 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
744
597
  createdFiles.push(f);
745
598
  }
746
599
  }
747
- // Отправляем буфер субагента в UI для отображения в ToolCallBlock
748
600
  const buf = subRenderer.getBuffer();
749
601
  if (!quiet && renderer?.writeToolBuffer) {
750
602
  renderer.writeToolBuffer({ lines: buf.lines, toolCalls: buf.toolCalls });
@@ -760,92 +612,7 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
760
612
  if (!quiet) {
761
613
  renderer?.writeToolResult(subTime, toolExitCode, toolOutput.slice(0, 300));
762
614
  }
763
- }
764
- else if (isBash && cmd.startsWith('plan ')) {
765
- const planArgs = cmd.slice(5).trim();
766
- const { action, params } = parsePlanCommand(planArgs);
767
- if (!quiet) {
768
- renderer?.writeBracketTag('plan', action);
769
- }
770
- switch (action) {
771
- case 'create':
772
- toolOutput = createPlan(params[0] || 'Untitled Plan', params.slice(1));
773
- break;
774
- case 'done':
775
- toolOutput = updatePlanStep(parseInt(params[0]) || 0, 'done');
776
- break;
777
- case 'progress':
778
- toolOutput = updatePlanStep(parseInt(params[0]) || 0, 'in_progress');
779
- break;
780
- case 'show':
781
- toolOutput = showPlan();
782
- break;
783
- case 'clear':
784
- toolOutput = clearPlan();
785
- break;
786
- default:
787
- toolOutput = 'Unknown plan action. Use: create, done, progress, show, clear';
788
- toolExitCode = 1;
789
- }
790
- }
791
- else if (isBash && cmd.startsWith('todo ')) {
792
- const todoArgs = cmd.slice(5).trim();
793
- const { action, params } = parseTodoCommand(todoArgs);
794
- if (!quiet) {
795
- renderer?.writeBracketTag('todo', action);
796
- }
797
- switch (action) {
798
- case 'add':
799
- toolOutput = addTodo(params);
800
- break;
801
- case 'done':
802
- toolOutput = doneTodo(params);
803
- break;
804
- case 'remove':
805
- case 'rm':
806
- toolOutput = removeTodo(params);
807
- break;
808
- case 'list':
809
- case 'ls':
810
- toolOutput = listTodos();
811
- break;
812
- default:
813
- toolOutput = 'Unknown todo action. Use: add, done, remove, list';
814
- toolExitCode = 1;
815
- }
816
- }
817
- else if (isBash && cmd.startsWith('question ')) {
818
- const qArgs = cmd.slice(9).trim();
819
- const { question, choices } = parseQuestionCommand(qArgs);
820
- if (!quiet) {
821
- renderer?.writeBracketTag('question', question);
822
- }
823
- messages.push({
824
- role: 'tool',
825
- content: `Question asked: ${question}`,
826
- tool_call_id: toolId,
827
- });
828
- return {
829
- messages,
830
- questionPending: { question, choices: choices.length > 0 ? choices : undefined },
831
- };
832
- }
833
- else if (isBash && cmd.startsWith('approve ')) {
834
- const approveArgs = cmd.slice(8).trim();
835
- const action = parseApproveCommand(approveArgs);
836
- if (!quiet) {
837
- renderer?.writeBracketTag('approve', action);
838
- }
839
- messages.push({
840
- role: 'tool',
841
- content: `Approval requested: ${action}`,
842
- tool_call_id: toolId,
843
- });
844
- return {
845
- messages,
846
- questionPending: { question: `Approve: ${action}?`, choices: ['Yes', 'No'] },
847
- };
848
- // ─── MCP-инструменты ────────────────────────────────
615
+ // ─── MCP: внешние tool-серверы ────────────────────────
849
616
  }
850
617
  else if (isBash && isMCPCommand(cmd)) {
851
618
  const mcpCmd = parseMCPCommand(cmd);
@@ -859,18 +626,35 @@ export async function runAgentLoop(provider, registry, prompt, contextManager, p
859
626
  toolOutput = 'Invalid MCP command. Format: mcp <server> <tool> [args_json]';
860
627
  toolExitCode = 1;
861
628
  }
862
- // ─── Файловые инструменты (не bash) ─────────────────
863
- }
864
- else if (!isBash) {
865
- const result = await registry.execute({ id: toolId, name: parsed.name, arguments: parsed.arguments });
866
- toolOutput = result.stdout || result.stderr || '(no output)';
867
- toolExitCode = result.exitCode;
868
- // ─── Bash: выполнение shell-команд ──────────────────
629
+ // ─── Все инструменты через ToolExecutor ──────────────
869
630
  }
870
631
  else {
871
- const result = await registry.execute({ id: toolId, name: 'bash', arguments: { cmd, timeout: parsed.arguments?.timeout ?? 120 } });
632
+ // Определяем инструмент и аргументы для executor'а
633
+ let executorName = parsed.name;
634
+ let executorArgs = parsed.arguments;
635
+ // Bash-команды
636
+ if (isBash) {
637
+ executorName = 'bash';
638
+ executorArgs = { cmd, timeout: parsed.arguments?.timeout ?? 120 };
639
+ }
640
+ const hookCmd = isBash ? cmd : `${parsed.name} ${JSON.stringify(parsed.arguments)}`;
641
+ const executorCall = { id: toolId, name: executorName, arguments: executorArgs };
642
+ const toolCtx = { cwd: process.cwd(), renderer, quiet };
643
+ const { result, elapsed } = await toolExecutor.execute(executorCall, toolCtx, hookCmd);
872
644
  toolOutput = result.stdout || result.stderr || '(no output)';
873
645
  toolExitCode = result.exitCode;
646
+ // Обработка question/approve: приостанавливаем цикл
647
+ if (result.questionPending) {
648
+ messages.push({
649
+ role: 'tool',
650
+ content: toolOutput,
651
+ tool_call_id: toolId,
652
+ });
653
+ return {
654
+ messages,
655
+ questionPending: { question: toolOutput, choices: undefined },
656
+ };
657
+ }
874
658
  }
875
659
  const toolTime = ((performance.now() - toolStart) / 1000).toFixed(1);
876
660
  logToolCall(parsed.name, preview, toolExitCode !== 0 ? `exit:${toolExitCode} ${toolOutput.slice(0, 100)}` : `ok (${toolTime}s)`);