@itphx/synth-core 0.2.0 → 0.3.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 (510) hide show
  1. package/definitions/protected-paths.json +36 -0
  2. package/definitions/shell-policy.json +36 -0
  3. package/dist/definitions/protected-paths.json +36 -0
  4. package/dist/definitions/shell-policy.json +36 -0
  5. package/dist/engine/agents/definitions/admin.json +3 -1
  6. package/dist/engine/agents/definitions/analyst.json +3 -1
  7. package/dist/engine/agents/definitions/assistant.json +2 -2
  8. package/dist/engine/agents/definitions/auditor.json +4 -1
  9. package/dist/engine/agents/definitions/developer.json +4 -1
  10. package/dist/engine/agents/definitions/devops.json +4 -1
  11. package/dist/engine/agents/definitions/explore.json +3 -3
  12. package/dist/engine/agents/definitions/general.json +2 -2
  13. package/dist/engine/agents/definitions/pm.json +5 -1
  14. package/dist/engine/agents/definitions/qa.json +4 -1
  15. package/dist/engine/agents/definitions/title.json +2 -2
  16. package/dist/engine/agents/index.d.ts +4 -0
  17. package/dist/engine/agents/index.d.ts.map +1 -1
  18. package/dist/engine/agents/index.js +9 -3
  19. package/dist/engine/agents/index.js.map +1 -1
  20. package/dist/engine/agents/mode.d.ts +5 -3
  21. package/dist/engine/agents/mode.d.ts.map +1 -1
  22. package/dist/engine/agents/mode.js +6 -6
  23. package/dist/engine/agents/mode.js.map +1 -1
  24. package/dist/engine/agents/mode.test.js +9 -3
  25. package/dist/engine/agents/mode.test.js.map +1 -1
  26. package/dist/engine/agents/normalize.d.ts +9 -0
  27. package/dist/engine/agents/normalize.d.ts.map +1 -0
  28. package/dist/engine/agents/normalize.js +22 -0
  29. package/dist/engine/agents/normalize.js.map +1 -0
  30. package/dist/engine/agents/normalize.test.d.ts +2 -0
  31. package/dist/engine/agents/normalize.test.d.ts.map +1 -0
  32. package/dist/engine/agents/normalize.test.js +37 -0
  33. package/dist/engine/agents/normalize.test.js.map +1 -0
  34. package/dist/engine/agents/registry.d.ts +2 -1
  35. package/dist/engine/agents/registry.d.ts.map +1 -1
  36. package/dist/engine/agents/registry.js +7 -6
  37. package/dist/engine/agents/registry.js.map +1 -1
  38. package/dist/engine/agents/registry.test.js +24 -0
  39. package/dist/engine/agents/registry.test.js.map +1 -1
  40. package/dist/engine/context/config.d.ts +43 -6
  41. package/dist/engine/context/config.d.ts.map +1 -1
  42. package/dist/engine/context/config.js +123 -26
  43. package/dist/engine/context/config.js.map +1 -1
  44. package/dist/engine/context/config.security.test.d.ts +2 -0
  45. package/dist/engine/context/config.security.test.d.ts.map +1 -0
  46. package/dist/engine/context/config.security.test.js +82 -0
  47. package/dist/engine/context/config.security.test.js.map +1 -0
  48. package/dist/engine/context/config.test.js +199 -10
  49. package/dist/engine/context/config.test.js.map +1 -1
  50. package/dist/engine/context/optimizer.d.ts +20 -1
  51. package/dist/engine/context/optimizer.d.ts.map +1 -1
  52. package/dist/engine/context/optimizer.js +63 -29
  53. package/dist/engine/context/optimizer.js.map +1 -1
  54. package/dist/engine/context/optimizer.test.js +156 -0
  55. package/dist/engine/context/optimizer.test.js.map +1 -1
  56. package/dist/engine/gateway/gateway-provider.d.ts +4 -0
  57. package/dist/engine/gateway/gateway-provider.d.ts.map +1 -1
  58. package/dist/engine/gateway/gateway-provider.js +7 -2
  59. package/dist/engine/gateway/gateway-provider.js.map +1 -1
  60. package/dist/engine/gateway/gateway-provider.test.js +3 -3
  61. package/dist/engine/gateway/gateway-provider.test.js.map +1 -1
  62. package/dist/engine/gateway/input-guard.d.ts.map +1 -1
  63. package/dist/engine/gateway/input-guard.js +2 -1
  64. package/dist/engine/gateway/input-guard.js.map +1 -1
  65. package/dist/engine/gateway/secret-detector.d.ts.map +1 -1
  66. package/dist/engine/gateway/secret-detector.js +2 -3
  67. package/dist/engine/gateway/secret-detector.js.map +1 -1
  68. package/dist/engine/gateway/secret-detector.test.js +4 -4
  69. package/dist/engine/gateway/secret-detector.test.js.map +1 -1
  70. package/dist/engine/llm-error.d.ts +23 -0
  71. package/dist/engine/llm-error.d.ts.map +1 -0
  72. package/dist/engine/llm-error.js +86 -0
  73. package/dist/engine/llm-error.js.map +1 -0
  74. package/dist/engine/llm-error.test.d.ts +2 -0
  75. package/dist/engine/llm-error.test.d.ts.map +1 -0
  76. package/dist/engine/llm-error.test.js +101 -0
  77. package/dist/engine/llm-error.test.js.map +1 -0
  78. package/dist/engine/orchestrator-deny-abort.test.d.ts +2 -0
  79. package/dist/engine/orchestrator-deny-abort.test.d.ts.map +1 -0
  80. package/dist/engine/orchestrator-deny-abort.test.js +414 -0
  81. package/dist/engine/orchestrator-deny-abort.test.js.map +1 -0
  82. package/dist/engine/orchestrator-error.test.d.ts +2 -0
  83. package/dist/engine/orchestrator-error.test.d.ts.map +1 -0
  84. package/dist/engine/orchestrator-error.test.js +136 -0
  85. package/dist/engine/orchestrator-error.test.js.map +1 -0
  86. package/dist/engine/orchestrator.d.ts +123 -10
  87. package/dist/engine/orchestrator.d.ts.map +1 -1
  88. package/dist/engine/orchestrator.js +791 -181
  89. package/dist/engine/orchestrator.js.map +1 -1
  90. package/dist/engine/orchestrator.test.js +723 -0
  91. package/dist/engine/orchestrator.test.js.map +1 -1
  92. package/dist/engine/permission/approval.d.ts.map +1 -1
  93. package/dist/engine/permission/approval.js +29 -10
  94. package/dist/engine/permission/approval.js.map +1 -1
  95. package/dist/engine/permission/approval.test.js +57 -0
  96. package/dist/engine/permission/approval.test.js.map +1 -1
  97. package/dist/engine/permission/evaluator.d.ts +13 -0
  98. package/dist/engine/permission/evaluator.d.ts.map +1 -1
  99. package/dist/engine/permission/evaluator.js +121 -22
  100. package/dist/engine/permission/evaluator.js.map +1 -1
  101. package/dist/engine/permission/evaluator.test.js +188 -4
  102. package/dist/engine/permission/evaluator.test.js.map +1 -1
  103. package/dist/engine/permission/groups.d.ts.map +1 -1
  104. package/dist/engine/permission/groups.js +19 -1
  105. package/dist/engine/permission/groups.js.map +1 -1
  106. package/dist/engine/permission/integration.test.js +165 -4
  107. package/dist/engine/permission/integration.test.js.map +1 -1
  108. package/dist/engine/plan.integration.test.js +142 -33
  109. package/dist/engine/plan.integration.test.js.map +1 -1
  110. package/dist/engine/router/micro/tasks/classify-intent.d.ts.map +1 -1
  111. package/dist/engine/router/micro/tasks/classify-intent.js +24 -18
  112. package/dist/engine/router/micro/tasks/classify-intent.js.map +1 -1
  113. package/dist/engine/router/micro/tasks/classify-intent.test.js +4 -4
  114. package/dist/engine/router/micro/tasks/classify-intent.test.js.map +1 -1
  115. package/dist/engine/router/micro/tasks/enum-decision.d.ts.map +1 -1
  116. package/dist/engine/router/micro/tasks/enum-decision.js +14 -8
  117. package/dist/engine/router/micro/tasks/enum-decision.js.map +1 -1
  118. package/dist/engine/router/micro/tasks/enum-decision.test.js +2 -2
  119. package/dist/engine/router/micro/tasks/enum-decision.test.js.map +1 -1
  120. package/dist/engine/router/micro/tasks/small-talk.d.ts.map +1 -1
  121. package/dist/engine/router/micro/tasks/small-talk.js +20 -17
  122. package/dist/engine/router/micro/tasks/small-talk.js.map +1 -1
  123. package/dist/engine/router/micro/types.d.ts +2 -0
  124. package/dist/engine/router/micro/types.d.ts.map +1 -1
  125. package/dist/engine/router/provider-pool.d.ts +2 -2
  126. package/dist/engine/router/provider-pool.d.ts.map +1 -1
  127. package/dist/engine/router/provider-pool.js +5 -3
  128. package/dist/engine/router/provider-pool.js.map +1 -1
  129. package/dist/engine/router/quality-checker.d.ts.map +1 -1
  130. package/dist/engine/router/quality-checker.js +15 -14
  131. package/dist/engine/router/quality-checker.js.map +1 -1
  132. package/dist/engine/router/router.d.ts +2 -2
  133. package/dist/engine/router/router.d.ts.map +1 -1
  134. package/dist/engine/router/router.js +8 -5
  135. package/dist/engine/router/router.js.map +1 -1
  136. package/dist/engine/router/utils.d.ts +2 -2
  137. package/dist/engine/router/utils.d.ts.map +1 -1
  138. package/dist/engine/router/utils.js +2 -1
  139. package/dist/engine/router/utils.js.map +1 -1
  140. package/dist/engine/security/index.d.ts +6 -0
  141. package/dist/engine/security/index.d.ts.map +1 -1
  142. package/dist/engine/security/index.js +3 -0
  143. package/dist/engine/security/index.js.map +1 -1
  144. package/dist/engine/security/integration.test.js +11 -11
  145. package/dist/engine/security/integration.test.js.map +1 -1
  146. package/dist/engine/security/prompt-injection.d.ts +1 -1
  147. package/dist/engine/security/prompt-injection.d.ts.map +1 -1
  148. package/dist/engine/security/prompt-injection.js +2 -1
  149. package/dist/engine/security/prompt-injection.js.map +1 -1
  150. package/dist/engine/security/prompt-injection.test.js +1 -1
  151. package/dist/engine/security/prompt-injection.test.js.map +1 -1
  152. package/dist/engine/security/protected-paths-store.d.ts +56 -0
  153. package/dist/engine/security/protected-paths-store.d.ts.map +1 -0
  154. package/dist/engine/security/protected-paths-store.js +239 -0
  155. package/dist/engine/security/protected-paths-store.js.map +1 -0
  156. package/dist/engine/security/protected-paths.d.ts +54 -0
  157. package/dist/engine/security/protected-paths.d.ts.map +1 -0
  158. package/dist/engine/security/protected-paths.js +193 -0
  159. package/dist/engine/security/protected-paths.js.map +1 -0
  160. package/dist/engine/security/protected-paths.test.d.ts +2 -0
  161. package/dist/engine/security/protected-paths.test.d.ts.map +1 -0
  162. package/dist/engine/security/protected-paths.test.js +166 -0
  163. package/dist/engine/security/protected-paths.test.js.map +1 -0
  164. package/dist/engine/security/shell-guard.d.ts +117 -0
  165. package/dist/engine/security/shell-guard.d.ts.map +1 -0
  166. package/dist/engine/security/shell-guard.js +578 -0
  167. package/dist/engine/security/shell-guard.js.map +1 -0
  168. package/dist/engine/security/shell-guard.test.d.ts +2 -0
  169. package/dist/engine/security/shell-guard.test.d.ts.map +1 -0
  170. package/dist/engine/security/shell-guard.test.js +378 -0
  171. package/dist/engine/security/shell-guard.test.js.map +1 -0
  172. package/dist/engine/system-messages.d.ts +237 -0
  173. package/dist/engine/system-messages.d.ts.map +1 -0
  174. package/dist/engine/system-messages.js +276 -0
  175. package/dist/engine/system-messages.js.map +1 -0
  176. package/dist/engine/system-messages.test.d.ts +2 -0
  177. package/dist/engine/system-messages.test.d.ts.map +1 -0
  178. package/dist/engine/system-messages.test.js +81 -0
  179. package/dist/engine/system-messages.test.js.map +1 -0
  180. package/dist/engine/tools/abort.d.ts +21 -0
  181. package/dist/engine/tools/abort.d.ts.map +1 -0
  182. package/dist/engine/tools/abort.js +44 -0
  183. package/dist/engine/tools/abort.js.map +1 -0
  184. package/dist/engine/tools/classify-conversation.d.ts.map +1 -1
  185. package/dist/engine/tools/classify-conversation.js +7 -2
  186. package/dist/engine/tools/classify-conversation.js.map +1 -1
  187. package/dist/engine/tools/fs-read.d.ts.map +1 -1
  188. package/dist/engine/tools/fs-read.js +17 -6
  189. package/dist/engine/tools/fs-read.js.map +1 -1
  190. package/dist/engine/tools/fs-read.test.js +43 -0
  191. package/dist/engine/tools/fs-read.test.js.map +1 -1
  192. package/dist/engine/tools/fs-write.d.ts.map +1 -1
  193. package/dist/engine/tools/fs-write.js +47 -17
  194. package/dist/engine/tools/fs-write.js.map +1 -1
  195. package/dist/engine/tools/git.d.ts +2 -1
  196. package/dist/engine/tools/git.d.ts.map +1 -1
  197. package/dist/engine/tools/git.js +85 -17
  198. package/dist/engine/tools/git.js.map +1 -1
  199. package/dist/engine/tools/git.test.js +39 -0
  200. package/dist/engine/tools/git.test.js.map +1 -1
  201. package/dist/engine/tools/index.d.ts.map +1 -1
  202. package/dist/engine/tools/index.js +16 -2
  203. package/dist/engine/tools/index.js.map +1 -1
  204. package/dist/engine/tools/json-schema.js +38 -0
  205. package/dist/engine/tools/json-schema.js.map +1 -1
  206. package/dist/engine/tools/json-schema.test.d.ts +2 -0
  207. package/dist/engine/tools/json-schema.test.d.ts.map +1 -0
  208. package/dist/engine/tools/json-schema.test.js +40 -0
  209. package/dist/engine/tools/json-schema.test.js.map +1 -0
  210. package/dist/engine/tools/path-utils.d.ts.map +1 -1
  211. package/dist/engine/tools/path-utils.js +24 -0
  212. package/dist/engine/tools/path-utils.js.map +1 -1
  213. package/dist/engine/tools/path-utils.test.js +15 -0
  214. package/dist/engine/tools/path-utils.test.js.map +1 -1
  215. package/dist/engine/tools/plan.d.ts +11 -3
  216. package/dist/engine/tools/plan.d.ts.map +1 -1
  217. package/dist/engine/tools/plan.js +326 -95
  218. package/dist/engine/tools/plan.js.map +1 -1
  219. package/dist/engine/tools/plan.test.js +161 -47
  220. package/dist/engine/tools/plan.test.js.map +1 -1
  221. package/dist/engine/tools/project-context.d.ts.map +1 -1
  222. package/dist/engine/tools/project-context.js +54 -42
  223. package/dist/engine/tools/project-context.js.map +1 -1
  224. package/dist/engine/tools/project-context.test.js +37 -22
  225. package/dist/engine/tools/project-context.test.js.map +1 -1
  226. package/dist/engine/tools/project-manage.d.ts +24 -0
  227. package/dist/engine/tools/project-manage.d.ts.map +1 -0
  228. package/dist/engine/tools/project-manage.js +311 -0
  229. package/dist/engine/tools/project-manage.js.map +1 -0
  230. package/dist/engine/tools/project-manage.test.d.ts +2 -0
  231. package/dist/engine/tools/project-manage.test.d.ts.map +1 -0
  232. package/dist/engine/tools/project-manage.test.js +196 -0
  233. package/dist/engine/tools/project-manage.test.js.map +1 -0
  234. package/dist/engine/tools/read-reference.d.ts.map +1 -1
  235. package/dist/engine/tools/read-reference.js +10 -5
  236. package/dist/engine/tools/read-reference.js.map +1 -1
  237. package/dist/engine/tools/read-reference.test.js +5 -2
  238. package/dist/engine/tools/read-reference.test.js.map +1 -1
  239. package/dist/engine/tools/search.d.ts.map +1 -1
  240. package/dist/engine/tools/search.js +16 -6
  241. package/dist/engine/tools/search.js.map +1 -1
  242. package/dist/engine/tools/shell.d.ts.map +1 -1
  243. package/dist/engine/tools/shell.js +17 -4
  244. package/dist/engine/tools/shell.js.map +1 -1
  245. package/dist/engine/tools/shell.test.js +63 -1
  246. package/dist/engine/tools/shell.test.js.map +1 -1
  247. package/dist/engine/tools/task-resume.d.ts +93 -0
  248. package/dist/engine/tools/task-resume.d.ts.map +1 -0
  249. package/dist/engine/tools/task-resume.js +269 -0
  250. package/dist/engine/tools/task-resume.js.map +1 -0
  251. package/dist/engine/tools/task-resume.test.d.ts +2 -0
  252. package/dist/engine/tools/task-resume.test.d.ts.map +1 -0
  253. package/dist/engine/tools/task-resume.test.js +423 -0
  254. package/dist/engine/tools/task-resume.test.js.map +1 -0
  255. package/dist/engine/tools/task.d.ts +81 -2
  256. package/dist/engine/tools/task.d.ts.map +1 -1
  257. package/dist/engine/tools/task.js +898 -471
  258. package/dist/engine/tools/task.js.map +1 -1
  259. package/dist/engine/tools/task.test.js +371 -3
  260. package/dist/engine/tools/task.test.js.map +1 -1
  261. package/dist/engine/tools/todo.d.ts +27 -1
  262. package/dist/engine/tools/todo.d.ts.map +1 -1
  263. package/dist/engine/tools/todo.js +320 -14
  264. package/dist/engine/tools/todo.js.map +1 -1
  265. package/dist/engine/tools/todo.test.js +150 -48
  266. package/dist/engine/tools/todo.test.js.map +1 -1
  267. package/dist/engine/tools/web.d.ts.map +1 -1
  268. package/dist/engine/tools/web.js +65 -18
  269. package/dist/engine/tools/web.js.map +1 -1
  270. package/dist/engine/tools/web.test.js +94 -2
  271. package/dist/engine/tools/web.test.js.map +1 -1
  272. package/dist/index.d.ts +8 -2
  273. package/dist/index.d.ts.map +1 -1
  274. package/dist/index.js +5 -2
  275. package/dist/index.js.map +1 -1
  276. package/dist/logger.d.ts.map +1 -1
  277. package/dist/logger.js +0 -1
  278. package/dist/logger.js.map +1 -1
  279. package/dist/main.js +3 -2
  280. package/dist/main.js.map +1 -1
  281. package/dist/prompts/roles/ADMIN.en.md +12 -11
  282. package/dist/prompts/roles/ADMIN.ru.md +12 -11
  283. package/dist/prompts/roles/ANALYST.en.md +12 -11
  284. package/dist/prompts/roles/ANALYST.ru.md +12 -11
  285. package/dist/prompts/roles/ASSISTANT.en.md +12 -11
  286. package/dist/prompts/roles/ASSISTANT.ru.md +12 -11
  287. package/dist/prompts/roles/AUDITOR.en.md +12 -11
  288. package/dist/prompts/roles/AUDITOR.ru.md +12 -11
  289. package/dist/prompts/roles/DEVELOPER.en.md +12 -11
  290. package/dist/prompts/roles/DEVELOPER.ru.md +12 -11
  291. package/dist/prompts/roles/DEVOPS.en.md +12 -11
  292. package/dist/prompts/roles/DEVOPS.ru.md +12 -11
  293. package/dist/prompts/roles/PM.en.md +12 -11
  294. package/dist/prompts/roles/PM.ru.md +12 -11
  295. package/dist/prompts/roles/QA.en.md +12 -11
  296. package/dist/prompts/roles/QA.ru.md +12 -11
  297. package/dist/prompts/roles/TITLE.en.md +30 -0
  298. package/dist/prompts/roles/TITLE.ru.md +3 -11
  299. package/dist/prompts/security/ask-protected-path.en.md +1 -0
  300. package/dist/prompts/security/ask-protected-path.ru.md +1 -0
  301. package/dist/prompts/security/deny-command.en.md +1 -0
  302. package/dist/prompts/security/deny-command.ru.md +1 -0
  303. package/dist/prompts/security/deny-protected-dir.en.md +1 -0
  304. package/dist/prompts/security/deny-protected-dir.ru.md +1 -0
  305. package/dist/prompts/security/deny-protected-path.en.md +1 -0
  306. package/dist/prompts/security/deny-protected-path.ru.md +1 -0
  307. package/dist/prompts/subagents/explore.en.md +23 -0
  308. package/dist/prompts/subagents/general.en.md +19 -0
  309. package/dist/prompts/system/MODE_ACT.en.md +20 -0
  310. package/{prompts/modes/act.md → dist/prompts/system/MODE_ACT.ru.md} +2 -0
  311. package/dist/prompts/system/MODE_PLAN.en.md +22 -0
  312. package/dist/prompts/{modes/plan.md → system/MODE_PLAN.ru.md} +4 -3
  313. package/dist/prompts/system/MODE_YOLO.en.md +22 -0
  314. package/{prompts/modes/yolo.md → dist/prompts/system/MODE_YOLO.ru.md} +2 -0
  315. package/dist/prompts/system/PLAN_GUIDE.en.md +36 -0
  316. package/dist/prompts/system/PLAN_GUIDE.ru.md +33 -0
  317. package/dist/prompts/system/PLAN_MODE_AUTO.en.md +11 -0
  318. package/dist/prompts/system/PLAN_MODE_AUTO.ru.md +12 -0
  319. package/dist/prompts/system/PLAN_MODE_STRICT_APPROVED.en.md +8 -0
  320. package/dist/prompts/system/PLAN_MODE_STRICT_APPROVED.ru.md +8 -0
  321. package/dist/prompts/system/PLAN_MODE_STRICT_NOT_APPROVED.en.md +7 -0
  322. package/dist/prompts/system/PLAN_MODE_STRICT_NOT_APPROVED.ru.md +7 -0
  323. package/dist/providers/anthropic.d.ts.map +1 -1
  324. package/dist/providers/anthropic.js +28 -2
  325. package/dist/providers/anthropic.js.map +1 -1
  326. package/dist/providers/base.d.ts +20 -1
  327. package/dist/providers/base.d.ts.map +1 -1
  328. package/dist/providers/base.js +17 -0
  329. package/dist/providers/base.js.map +1 -1
  330. package/dist/providers/exceptions.d.ts.map +1 -1
  331. package/dist/providers/exceptions.js +3 -2
  332. package/dist/providers/exceptions.js.map +1 -1
  333. package/dist/providers/ollama.d.ts +23 -0
  334. package/dist/providers/ollama.d.ts.map +1 -1
  335. package/dist/providers/ollama.js +128 -12
  336. package/dist/providers/ollama.js.map +1 -1
  337. package/dist/providers/ollama.test.d.ts +2 -0
  338. package/dist/providers/ollama.test.d.ts.map +1 -0
  339. package/dist/providers/ollama.test.js +129 -0
  340. package/dist/providers/ollama.test.js.map +1 -0
  341. package/dist/providers/openai.d.ts.map +1 -1
  342. package/dist/providers/openai.js +82 -9
  343. package/dist/providers/openai.js.map +1 -1
  344. package/dist/providers/openai.test.d.ts +2 -0
  345. package/dist/providers/openai.test.d.ts.map +1 -0
  346. package/dist/providers/openai.test.js +84 -0
  347. package/dist/providers/openai.test.js.map +1 -0
  348. package/dist/providers/stream-error.test.d.ts +2 -0
  349. package/dist/providers/stream-error.test.d.ts.map +1 -0
  350. package/dist/providers/stream-error.test.js +63 -0
  351. package/dist/providers/stream-error.test.js.map +1 -0
  352. package/dist/providers/timeout.test.d.ts +2 -0
  353. package/dist/providers/timeout.test.d.ts.map +1 -0
  354. package/dist/providers/timeout.test.js +129 -0
  355. package/dist/providers/timeout.test.js.map +1 -0
  356. package/dist/queue/processor.d.ts +22 -0
  357. package/dist/queue/processor.d.ts.map +1 -1
  358. package/dist/queue/processor.js +150 -25
  359. package/dist/queue/processor.js.map +1 -1
  360. package/dist/queue/processor.test.d.ts +2 -0
  361. package/dist/queue/processor.test.d.ts.map +1 -0
  362. package/dist/queue/processor.test.js +211 -0
  363. package/dist/queue/processor.test.js.map +1 -0
  364. package/dist/server/app.d.ts.map +1 -1
  365. package/dist/server/app.js +30 -8
  366. package/dist/server/app.js.map +1 -1
  367. package/dist/server/chat.d.ts.map +1 -1
  368. package/dist/server/chat.js +18 -2
  369. package/dist/server/chat.js.map +1 -1
  370. package/dist/server/routes/agents.d.ts.map +1 -1
  371. package/dist/server/routes/agents.js +39 -23
  372. package/dist/server/routes/agents.js.map +1 -1
  373. package/dist/server/routes/fs.d.ts.map +1 -1
  374. package/dist/server/routes/fs.js +185 -6
  375. package/dist/server/routes/fs.js.map +1 -1
  376. package/dist/server/routes/models.d.ts.map +1 -1
  377. package/dist/server/routes/models.js +3 -1
  378. package/dist/server/routes/models.js.map +1 -1
  379. package/dist/server/routes/queue.d.ts.map +1 -1
  380. package/dist/server/routes/queue.js +91 -17
  381. package/dist/server/routes/queue.js.map +1 -1
  382. package/dist/server/routes/sessions.d.ts +2 -1
  383. package/dist/server/routes/sessions.d.ts.map +1 -1
  384. package/dist/server/routes/sessions.js +68 -10
  385. package/dist/server/routes/sessions.js.map +1 -1
  386. package/dist/server/routes/sessions.test.js +102 -2
  387. package/dist/server/routes/sessions.test.js.map +1 -1
  388. package/dist/server/routes/settings.d.ts +6 -1
  389. package/dist/server/routes/settings.d.ts.map +1 -1
  390. package/dist/server/routes/settings.js +138 -0
  391. package/dist/server/routes/settings.js.map +1 -1
  392. package/dist/server/routes/settings.test.js +65 -1
  393. package/dist/server/routes/settings.test.js.map +1 -1
  394. package/dist/server/serialize.d.ts +9 -3
  395. package/dist/server/serialize.d.ts.map +1 -1
  396. package/dist/server/serialize.js +9 -3
  397. package/dist/server/serialize.js.map +1 -1
  398. package/dist/sessions/auto-title.d.ts +1 -1
  399. package/dist/sessions/auto-title.d.ts.map +1 -1
  400. package/dist/sessions/auto-title.js +24 -13
  401. package/dist/sessions/auto-title.js.map +1 -1
  402. package/dist/sessions/manager.d.ts +1 -2
  403. package/dist/sessions/manager.d.ts.map +1 -1
  404. package/dist/sessions/manager.js +11 -36
  405. package/dist/sessions/manager.js.map +1 -1
  406. package/dist/sessions/manager.test.js +2 -10
  407. package/dist/sessions/manager.test.js.map +1 -1
  408. package/dist/storage/file-store.d.ts.map +1 -1
  409. package/dist/storage/file-store.js +2 -6
  410. package/dist/storage/file-store.js.map +1 -1
  411. package/dist/storage/file-store.test.js +1 -6
  412. package/dist/storage/file-store.test.js.map +1 -1
  413. package/dist/storage/project-store.d.ts.map +1 -1
  414. package/dist/storage/project-store.js +12 -0
  415. package/dist/storage/project-store.js.map +1 -1
  416. package/dist/storage/runtime-settings.d.ts +25 -1
  417. package/dist/storage/runtime-settings.d.ts.map +1 -1
  418. package/dist/storage/runtime-settings.js +31 -0
  419. package/dist/storage/runtime-settings.js.map +1 -1
  420. package/package.json +6 -5
  421. package/prompts/roles/TITLE.en.md +30 -0
  422. package/prompts/security/ask-protected-path.en.md +1 -0
  423. package/prompts/security/ask-protected-path.ru.md +1 -0
  424. package/prompts/security/deny-command.en.md +1 -0
  425. package/prompts/security/deny-command.ru.md +1 -0
  426. package/prompts/security/deny-protected-dir.en.md +1 -0
  427. package/prompts/security/deny-protected-dir.ru.md +1 -0
  428. package/prompts/security/deny-protected-path.en.md +1 -0
  429. package/prompts/security/deny-protected-path.ru.md +1 -0
  430. package/prompts/subagents/explore.en.md +23 -0
  431. package/prompts/subagents/general.en.md +19 -0
  432. package/prompts/system/MODE_ACT.en.md +20 -0
  433. package/{dist/prompts/modes/act.md → prompts/system/MODE_ACT.ru.md} +2 -0
  434. package/prompts/system/MODE_PLAN.en.md +22 -0
  435. package/{dist/prompts/prompts/modes/plan.md → prompts/system/MODE_PLAN.ru.md} +4 -3
  436. package/prompts/system/MODE_YOLO.en.md +22 -0
  437. package/{dist/prompts/prompts/modes/yolo.md → prompts/system/MODE_YOLO.ru.md} +2 -0
  438. package/prompts/system/PLAN_GUIDE.en.md +36 -0
  439. package/prompts/system/PLAN_GUIDE.ru.md +33 -0
  440. package/prompts/system/PLAN_MODE_AUTO.en.md +11 -0
  441. package/prompts/system/PLAN_MODE_AUTO.ru.md +12 -0
  442. package/prompts/system/PLAN_MODE_STRICT_APPROVED.en.md +8 -0
  443. package/prompts/system/PLAN_MODE_STRICT_APPROVED.ru.md +8 -0
  444. package/prompts/system/PLAN_MODE_STRICT_NOT_APPROVED.en.md +7 -0
  445. package/prompts/system/PLAN_MODE_STRICT_NOT_APPROVED.ru.md +7 -0
  446. package/dist/dictionaries/dictionaries/coding.en.yaml +0 -20
  447. package/dist/dictionaries/dictionaries/coding.ru.yaml +0 -22
  448. package/dist/dictionaries/dictionaries/coding.tools.yaml +0 -12
  449. package/dist/dictionaries/dictionaries/enum.en.yaml +0 -54
  450. package/dist/dictionaries/dictionaries/enum.ru.yaml +0 -67
  451. package/dist/dictionaries/dictionaries/injection.en.yaml +0 -51
  452. package/dist/dictionaries/dictionaries/injection.ru.yaml +0 -51
  453. package/dist/dictionaries/dictionaries/intents.en.yaml +0 -68
  454. package/dist/dictionaries/dictionaries/intents.ru.yaml +0 -78
  455. package/dist/dictionaries/dictionaries/research.en.yaml +0 -17
  456. package/dist/dictionaries/dictionaries/research.ru.yaml +0 -17
  457. package/dist/dictionaries/dictionaries/small-talk.en.yaml +0 -67
  458. package/dist/dictionaries/dictionaries/small-talk.ru.yaml +0 -81
  459. package/dist/prompts/modes/yolo.md +0 -20
  460. package/dist/prompts/prompts/modes/act.md +0 -18
  461. package/dist/prompts/prompts/roles/ADMIN.en.md +0 -33
  462. package/dist/prompts/prompts/roles/ADMIN.ru.md +0 -33
  463. package/dist/prompts/prompts/roles/ANALYST.en.md +0 -33
  464. package/dist/prompts/prompts/roles/ANALYST.ru.md +0 -33
  465. package/dist/prompts/prompts/roles/ASSISTANT.en.md +0 -38
  466. package/dist/prompts/prompts/roles/ASSISTANT.ru.md +0 -38
  467. package/dist/prompts/prompts/roles/AUDITOR.en.md +0 -33
  468. package/dist/prompts/prompts/roles/AUDITOR.ru.md +0 -33
  469. package/dist/prompts/prompts/roles/DEVELOPER.en.md +0 -42
  470. package/dist/prompts/prompts/roles/DEVELOPER.ru.md +0 -42
  471. package/dist/prompts/prompts/roles/DEVOPS.en.md +0 -35
  472. package/dist/prompts/prompts/roles/DEVOPS.ru.md +0 -35
  473. package/dist/prompts/prompts/roles/PM.en.md +0 -33
  474. package/dist/prompts/prompts/roles/PM.ru.md +0 -33
  475. package/dist/prompts/prompts/roles/QA.en.md +0 -33
  476. package/dist/prompts/prompts/roles/QA.ru.md +0 -33
  477. package/dist/prompts/prompts/roles/TITLE.ru.md +0 -30
  478. package/dist/prompts/prompts/subagents/explore.ru.md +0 -23
  479. package/dist/prompts/prompts/subagents/general.ru.md +0 -19
  480. package/dist/prompts/prompts/system/COMPRESSED_DIALOG.en.md +0 -5
  481. package/dist/prompts/prompts/system/COMPRESSED_DIALOG.ru.md +0 -5
  482. package/dist/prompts/prompts/system/INTERVIEW.en.md +0 -18
  483. package/dist/prompts/prompts/system/INTERVIEW.ru.md +0 -18
  484. package/dist/prompts/prompts/system/PARTICIPANTS_NOTICE.en.md +0 -7
  485. package/dist/prompts/prompts/system/PARTICIPANTS_NOTICE.ru.md +0 -7
  486. package/dist/prompts/prompts/system/ROLES_DESCRIPTION.en.md +0 -16
  487. package/dist/prompts/prompts/system/ROLES_DESCRIPTION.ru.md +0 -16
  488. package/dist/prompts/prompts/system/ROUTING.en.md +0 -13
  489. package/dist/prompts/prompts/system/ROUTING.md +0 -6
  490. package/dist/prompts/prompts/system/ROUTING.ru.md +0 -13
  491. package/dist/prompts/prompts/system/RULES.en.md +0 -10
  492. package/dist/prompts/prompts/system/RULES.md +0 -10
  493. package/dist/prompts/prompts/system/RULES.ru.md +0 -10
  494. package/dist/prompts/prompts/system/STICKY_NOTES.en.md +0 -6
  495. package/dist/prompts/prompts/system/STICKY_NOTES.ru.md +0 -6
  496. package/dist/prompts/prompts/system/SUMMARIZER.en.md +0 -54
  497. package/dist/prompts/prompts/system/SUMMARIZER.ru.md +0 -54
  498. package/dist/prompts/prompts/system/USER_CONTEXT.en.md +0 -5
  499. package/dist/prompts/prompts/system/USER_CONTEXT.ru.md +0 -5
  500. package/dist/prompts/system/ROUTING.md +0 -6
  501. package/dist/prompts/system/RULES.md +0 -10
  502. package/dist/server/routes/i18n.d.ts +0 -3
  503. package/dist/server/routes/i18n.d.ts.map +0 -1
  504. package/dist/server/routes/i18n.js +0 -83
  505. package/dist/server/routes/i18n.js.map +0 -1
  506. package/prompts/modes/plan.md +0 -20
  507. package/prompts/system/ROUTING.md +0 -6
  508. package/prompts/system/RULES.md +0 -10
  509. /package/dist/prompts/{prompts/system → system}/GATEWAY.en.md +0 -0
  510. /package/dist/prompts/{prompts/system → system}/GATEWAY.ru.md +0 -0
@@ -1,13 +1,47 @@
1
1
  import { z } from 'zod';
2
- import { ToolCapability, MessageRole, nowISO, generateId, makeTextPart, TsmState } from '@itphx/synth-shared';
2
+ import { ToolCapability, MessageRole, nowISO, generateId, makeTextPart } from '@itphx/synth-shared';
3
+ import { normalizeAgentModes } from '../agents/normalize.js';
3
4
  import { EventBus } from '../../events/bus.js';
5
+ import { classifyLlmError, isLlmErrorRecoverable, LLM_MAX_ATTEMPTS, LLM_RETRY_DELAYS_MS } from '../llm-error.js';
4
6
  import { deriveSubagentPermission } from '../permission/derive.js';
5
- import { PermissionEvaluator } from '../permission/evaluator.js';
7
+ import { PermissionEvaluator, isPlanRestricted, isPlanBlockedTool, getMcpToolAvailability } from '../permission/evaluator.js';
6
8
  import { PermissionApprovalManager } from '../permission/approval.js';
7
9
  import { serializeSession, serializeMessage } from '../../server/serialize.js';
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { t as sm, i18n as i18nMeta } from '../system-messages.js';
8
13
  const DEFAULT_SUBAGENT_MAX_STEPS = 15;
9
14
  const DEFAULT_LAST_N = 4;
10
- function messageToLLMMessage(msg) {
15
+ /** Retry саб-агента: максимум попыток LLM-вызова (единая политика — LLM_MAX_ATTEMPTS). */
16
+ export const SUBAGENT_MAX_ATTEMPTS = LLM_MAX_ATTEMPTS;
17
+ /** Backoff между попытками (единая политика — LLM_RETRY_DELAYS_MS). */
18
+ export const SUBAGENT_RETRY_DELAYS_MS = LLM_RETRY_DELAYS_MS;
19
+ /**
20
+ * Промпт саб-агента по языку сессии: prompts/subagents/{type}.{lang}.md →
21
+ * {type}.md → agentDef.subagentPrompt (definitions JSON, легаси).
22
+ * Каталог промптов — systemPromptsDir из ContextConfig (резолв prompts/ или dist/prompts).
23
+ */
24
+ export function resolveSubagentPrompt(agentDef, subagentType, language, systemPromptsDir) {
25
+ if (systemPromptsDir) {
26
+ const lang = language === 'en' ? 'en' : 'ru';
27
+ const localizedPath = join(systemPromptsDir, 'subagents', `${subagentType}.${lang}.md`);
28
+ if (existsSync(localizedPath)) {
29
+ try {
30
+ return readFileSync(localizedPath, 'utf-8');
31
+ }
32
+ catch { /* fallback ниже */ }
33
+ }
34
+ const basePath = join(systemPromptsDir, 'subagents', `${subagentType}.md`);
35
+ if (existsSync(basePath)) {
36
+ try {
37
+ return readFileSync(basePath, 'utf-8');
38
+ }
39
+ catch { /* fallback ниже */ }
40
+ }
41
+ }
42
+ return agentDef.subagentPrompt || '';
43
+ }
44
+ export function messageToLLMMessage(msg) {
11
45
  const roleMap = {
12
46
  [MessageRole.USER]: 'user',
13
47
  [MessageRole.ASSISTANT]: 'assistant',
@@ -27,9 +61,10 @@ function messageToLLMMessage(msg) {
27
61
  pinned: false,
28
62
  branchId: '',
29
63
  toolCallId: msg.toolCallId,
64
+ toolUse: msg.toolCalls,
30
65
  };
31
66
  }
32
- async function publishMessageAdded(eventBus, parentSessionId, childSessionId, msg, index, childSession) {
67
+ export async function publishMessageAdded(eventBus, parentSessionId, childSessionId, msg, index, childSession) {
33
68
  const serialized = serializeMessage(msg, index);
34
69
  const payload = {
35
70
  session_id: childSessionId,
@@ -73,44 +108,42 @@ export const taskTool = {
73
108
  async execute(args, ctx) {
74
109
  const parsed = this.parameters.safeParse(args);
75
110
  if (!parsed.success) {
76
- return { content: `Ошибка аргументов инструмента "${this.name}": ${parsed.error.issues.map(i => `${i.path.join('.') || 'root'}: ${i.message}`).join('; ')}`, isError: true };
111
+ return { content: sm('tool.args_error', { tool: this.name, issues: parsed.error.issues.map(i => `${i.path.join('.') || 'root'}: ${i.message}`).join('; ') }), isError: true, metadata: { i18n: i18nMeta('tool.args_error', { tool: this.name }) } };
77
112
  }
78
113
  const params = parsed.data;
79
114
  const extra = ctx.extra;
80
115
  if (!extra?.provider || !extra?.agentRegistry) {
81
116
  return {
82
- content: 'Ошибка: провайдер или реестр агентов недоступны для запуска сабагента',
117
+ content: sm('task.deps_unavailable'),
83
118
  isError: true,
84
119
  };
85
120
  }
86
121
  const agentDef = extra.agentRegistry.get(params.subagent_type);
87
122
  if (!agentDef) {
88
123
  return {
89
- content: `Сабагент "${params.subagent_type}" не найден. Доступные типы: ${extra.agentRegistry.getSubagents().map((a) => a.id).join(', ')}`,
124
+ content: sm('task.subagent_not_found', { type: params.subagent_type, types: extra.agentRegistry.getSubagents().map((a) => a.id).join(', ') }),
90
125
  isError: true,
91
126
  };
92
127
  }
93
- if (agentDef.agentMode !== 'subagent' && agentDef.agentMode !== 'all') {
128
+ if (!normalizeAgentModes(agentDef.agentMode).includes('subagent')) {
94
129
  return {
95
- content: `Агент "${params.subagent_type}" не может использоваться как сабагент (agentMode: ${agentDef.agentMode ?? 'primary'})`,
130
+ content: sm('task.subagent_not_allowed', { type: params.subagent_type, modes: String(agentDef.agentMode) }),
96
131
  isError: true,
97
132
  };
98
133
  }
99
134
  const contextMode = params.context_mode || 'none';
100
135
  const contextCount = params.context_count || DEFAULT_LAST_N;
101
- const systemPrompt = agentDef.subagentPrompt || '';
136
+ const systemPrompt = resolveSubagentPrompt(agentDef, params.subagent_type, extra.parentSessionSettings?.language, extra.contextConfig?.systemPromptsDir);
102
137
  const tools = extra.agentRegistry.getToolsForAgent(agentDef.id);
103
138
  const maxSteps = agentDef.maxSteps ?? DEFAULT_SUBAGENT_MAX_STEPS;
104
- const effectiveMaxSteps = (maxSteps === 0)
105
- ? Number.POSITIVE_INFINITY
106
- : maxSteps;
107
- const publish = extra.publish;
108
139
  // ─── Настройки роутинга для дочерней сессии (AD-034 Фаза 4) ──
109
140
  const childRoutingOverrides = {};
110
141
  if (params.model)
111
142
  childRoutingOverrides.model = params.model;
112
143
  if (params.provider)
113
144
  childRoutingOverrides.provider = params.provider;
145
+ if (agentDef?.reasoningLevel)
146
+ childRoutingOverrides.reasoningLevel = agentDef.reasoningLevel;
114
147
  if (params.routing_strategy) {
115
148
  if (params.routing_strategy === 'none') {
116
149
  childRoutingOverrides.routingEnabled = false;
@@ -149,7 +182,7 @@ export const taskTool = {
149
182
  const effectiveMode = contextMode === 'summary' && !extra.contextOptimizer ? 'none' : contextMode;
150
183
  if (effectiveMode === 'inherit') {
151
184
  const fork = await extra.sessionManager.forkSession(extra.sessionId, {
152
- title: `Подзадача: ${params.description}`,
185
+ title: sm('session.subtask_title', { description: params.description }),
153
186
  });
154
187
  fork.sessionOrigin = 'subagent';
155
188
  fork.userId = parentSession.userId;
@@ -164,7 +197,7 @@ export const taskTool = {
164
197
  const startIdx = Math.max(0, parentSession.messages.length - contextCount);
165
198
  const fork = await extra.sessionManager.forkSession(extra.sessionId, {
166
199
  messageIndex: startIdx,
167
- title: `Подзадача: ${params.description}`,
200
+ title: sm('session.subtask_title', { description: params.description }),
168
201
  });
169
202
  fork.sessionOrigin = 'subagent';
170
203
  fork.userId = parentSession.userId;
@@ -177,7 +210,7 @@ export const taskTool = {
177
210
  }
178
211
  else if (effectiveMode === 'summary') {
179
212
  const fork = await extra.sessionManager.forkSession(extra.sessionId, {
180
- title: `Подзадача: ${params.description}`,
213
+ title: sm('session.subtask_title', { description: params.description }),
181
214
  });
182
215
  fork.sessionOrigin = 'subagent';
183
216
  fork.userId = parentSession.userId;
@@ -223,7 +256,7 @@ export const taskTool = {
223
256
  else {
224
257
  // none mode (default)
225
258
  const child = await extra.sessionManager.createSession({
226
- title: `Подзадача: ${params.description}`,
259
+ title: sm('session.subtask_title', { description: params.description }),
227
260
  projectId: parentSession.projectId,
228
261
  engine: parentSession.engine,
229
262
  parentId: extra.sessionId,
@@ -382,49 +415,275 @@ export const taskTool = {
382
415
  // не фатально
383
416
  }
384
417
  }
385
- const cancelToken = { cancelled: false, cancel: () => { cancelToken.cancelled = true; } };
386
- // ─── Permissions саб-агента (AD-029): проверка перед каждым tool_call ──
387
- // Дочерняя сессия наследует permissions родителя (deriveSubagentPermission).
388
- // ask-запросы публикуются в EventBus РОДИТЕЛЬСКОЙ сессии — подтверждение
389
- // видит пользователь основной сессии, approval_id связывает ответ.
390
- const childEvaluator = new PermissionEvaluator(derivedPermission);
391
- const childApprovalManager = new PermissionApprovalManager(extra.eventBus ?? new EventBus());
392
- let childSessionMode = 'act';
393
- let childPlanApproved = false;
394
- let childPlanMode = 'auto';
395
- let childMcpToolSettings;
396
- if (childSessionId && extra.sessionManager) {
397
- try {
398
- const child = await extra.sessionManager.getSession(childSessionId);
399
- if (child) {
400
- childSessionMode = child.mode ?? 'act';
401
- childPlanApproved = Boolean(child.planContent);
402
- childPlanMode = child.settings.planMode ?? 'auto';
403
- childMcpToolSettings = child.settings.mcpToolSettings;
404
- }
418
+ // ─── Запуск цикла саб-агента: общий код для task и resume (ниже) ──
419
+ return runSubagent({
420
+ subagentType: params.subagent_type,
421
+ description: params.description,
422
+ contextMode,
423
+ maxSteps,
424
+ systemPrompt,
425
+ llmSystemPrompt,
426
+ initialMessages: messages.slice(1),
427
+ provider: effectiveProvider,
428
+ tools,
429
+ childSessionId,
430
+ parentSessionId: extra.sessionId,
431
+ routingDecisionInfo,
432
+ extra,
433
+ ctx,
434
+ retry: extra.retry,
435
+ });
436
+ },
437
+ };
438
+ /** Пауза backoff порциями с проверкой отмены. Возвращает true, если отмена. */
439
+ async function sleepAbortable(ms, isAborted) {
440
+ const SLICE_MS = 200;
441
+ let waited = 0;
442
+ while (waited < ms) {
443
+ if (isAborted())
444
+ return true;
445
+ await new Promise((resolve) => setTimeout(resolve, Math.min(SLICE_MS, ms - waited)));
446
+ waited += SLICE_MS;
447
+ }
448
+ return isAborted();
449
+ }
450
+ export async function runSubagent(config) {
451
+ const { subagentType, description, contextMode, maxSteps, systemPrompt, llmSystemPrompt, initialMessages, provider: effectiveProvider, tools: toolsInput, childSessionId, parentSessionId, extra, ctx, } = config;
452
+ let tools = toolsInput;
453
+ let routingDecisionInfo = config.routingDecisionInfo;
454
+ const publish = extra.publish;
455
+ const effectiveMaxSteps = (!maxSteps || maxSteps === 0)
456
+ ? Number.POSITIVE_INFINITY
457
+ : maxSteps;
458
+ // ─── Retry-параметры (аналог QueueProcessor: 3 попытки, backoff 5/15/30с) ──
459
+ const retryCfg = config.retry ?? extra.retry
460
+ ?? { maxRetries: LLM_MAX_ATTEMPTS, delaysMs: LLM_RETRY_DELAYS_MS };
461
+ const retryMaxAttempts = Math.max(1, retryCfg.maxRetries);
462
+ const retryDelaysMs = retryCfg.delaysMs.length > 0 ? retryCfg.delaysMs : LLM_RETRY_DELAYS_MS;
463
+ // ─── Permissions дочерней сессии (AD-029): родитель + deny-дополнения ──
464
+ const derivedPermission = deriveSubagentPermission(extra.permissionConfig);
465
+ // ─── LLM-сообщения: system + начальные (история/промпт) ─────────
466
+ const messages = [
467
+ {
468
+ id: '',
469
+ role: 'system',
470
+ content: llmSystemPrompt,
471
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
472
+ disabled: false,
473
+ pinned: false,
474
+ branchId: '',
475
+ },
476
+ ...initialMessages,
477
+ ];
478
+ const cancelToken = { cancelled: false, cancel: () => { cancelToken.cancelled = true; } };
479
+ // Связка локального cancel-токена саб-агента с родительским сигналом отмены:
480
+ // «Стоп» в родительской сессии (POST /sessions/:id/abort → orchestrator.cancel
481
+ // → внутренний toolAbortController.abort → ctx.abortSignal) должен немедленно
482
+ // прерывать LLM-стрим саб-агента и его цикл, а не ждать завершения всех шагов.
483
+ const isAborted = () => cancelToken.cancelled || ctx.abortSignal?.aborted === true;
484
+ if (ctx.abortSignal?.aborted) {
485
+ cancelToken.cancel();
486
+ }
487
+ else {
488
+ ctx.abortSignal?.addEventListener('abort', () => cancelToken.cancel(), { once: true });
489
+ }
490
+ // ─── Permissions саб-агента (AD-029): проверка перед каждым tool_call ──
491
+ // Дочерняя сессия наследует permissions родителя (deriveSubagentPermission).
492
+ // ask-запросы публикуются в EventBus РОДИТЕЛЬСКОЙ сессии — подтверждение
493
+ // видит пользователь основной сессии, approval_id связывает ответ.
494
+ const childEvaluator = new PermissionEvaluator(derivedPermission);
495
+ const childApprovalManager = new PermissionApprovalManager(extra.eventBus ?? new EventBus());
496
+ let childSessionMode = 'act';
497
+ let childPlanApproved = false;
498
+ let childPlanMode = 'auto';
499
+ let childMcpToolSettings;
500
+ if (childSessionId && extra.sessionManager) {
501
+ try {
502
+ const child = await extra.sessionManager.getSession(childSessionId);
503
+ if (child) {
504
+ childSessionMode = child.mode ?? 'act';
505
+ childPlanApproved = Boolean(child.planApproved);
506
+ childPlanMode = child.settings.planMode ?? 'auto';
507
+ childMcpToolSettings = child.settings.mcpToolSettings;
405
508
  }
406
- catch {
407
- // не фатально — остаёмся в act/auto
509
+ }
510
+ catch {
511
+ // не фатально — остаёмся в act/auto
512
+ }
513
+ }
514
+ // B5: режимная фильтрация списка тулов саб-агента (аналог
515
+ // orchestrator._filterToolsByMode). В plan-режиме (или strict без
516
+ // утверждённого плана) скрываем write/edit-тулы и git-запись — модель не
517
+ // видит инструменты, которые всё равно будут отклонены evaluator'ом.
518
+ if (isPlanRestricted(childSessionMode, childPlanMode, childPlanApproved) && tools) {
519
+ const filtered = [];
520
+ for (const tool of tools) {
521
+ const toolName = tool.function.name;
522
+ const toolDef = extra.toolRegistry?.get(toolName);
523
+ const capabilities = toolDef?.capabilities;
524
+ if (isPlanBlockedTool(toolName, capabilities))
525
+ continue;
526
+ // MCP-инструменты: в plan-режиме недоступный по настройкам тул убираем.
527
+ if (capabilities?.includes(ToolCapability.MCP) && childMcpToolSettings) {
528
+ const availability = getMcpToolAvailability(childMcpToolSettings, toolName);
529
+ if (availability && !availability.plan)
530
+ continue;
408
531
  }
532
+ filtered.push(tool);
409
533
  }
534
+ tools = filtered;
535
+ }
536
+ // Финализация дочерней сессии: обновление updatedAt + события для UI.
537
+ // Отдельного статусного поля «завершена/прервана» у сессии нет
538
+ // (SessionStatus — только процессная блокировка blocked/blockedAt),
539
+ // поэтому факт прерывания/ошибки фиксируется metadata-маркерами
540
+ // в сообщениях истории дочерней сессии.
541
+ const finalizeChildSession = async () => {
542
+ if (!childSessionId || !extra.sessionManager)
543
+ return;
410
544
  try {
411
- let finalContent = '';
412
- let iteration = 0;
413
- const doomTracker = new Map();
414
- let subagentReasoning = '';
415
- let subagentDenied = false;
416
- const allSubagentReasoning = [];
417
- const allSubagentIntermediateTexts = [];
418
- const allSubagentToolCalls = [];
419
- while (iteration < effectiveMaxSteps) {
420
- if (cancelToken.cancelled)
545
+ const childSession = await extra.sessionManager.getSession(childSessionId);
546
+ if (childSession) {
547
+ childSession.updatedAt = nowISO();
548
+ await extra.sessionManager.updateSession(childSession);
549
+ if (extra.eventBus) {
550
+ extra.eventBus.publish(childSessionId, 'session.updated', {
551
+ session_id: childSessionId,
552
+ });
553
+ extra.eventBus.publish(parentSessionId, 'child.session.updated', {
554
+ session: serializeSession(childSession),
555
+ });
556
+ }
557
+ }
558
+ }
559
+ catch {
560
+ // не фатально
561
+ }
562
+ };
563
+ /** Добавить сообщение в дочернюю сессию + опубликовать события. */
564
+ const addChildMessage = async (msg) => {
565
+ if (!childSessionId || !extra.sessionManager)
566
+ return;
567
+ try {
568
+ const updated = await extra.sessionManager.addMessage(childSessionId, msg);
569
+ if (updated && extra.eventBus) {
570
+ const idx = updated.messages.length - 1;
571
+ await publishMessageAdded(extra.eventBus, parentSessionId, childSessionId, updated.messages[idx], idx, updated);
572
+ }
573
+ }
574
+ catch {
575
+ // не фатально
576
+ }
577
+ };
578
+ try {
579
+ let finalContent = '';
580
+ let iteration = 0;
581
+ const doomTracker = new Map();
582
+ let subagentReasoning = '';
583
+ let subagentDenied = false;
584
+ const allSubagentReasoning = [];
585
+ const allSubagentIntermediateTexts = [];
586
+ const allSubagentToolCalls = [];
587
+ // Завершение по отмене (Стоп в родительской сессии): сохранить частичный
588
+ // текст (если есть), поставить маркер прерывания в историю дочерней
589
+ // сессии, финализировать её (без статуса completed) и вернуть родителю
590
+ // tool_result с маркером остановки, частичным ответом и списком шагов.
591
+ const finishAborted = async (partialContent, partialReasoning) => {
592
+ // 1. Частичный стрим оборванной итерации — в историю дочерней сессии
593
+ if (partialContent) {
594
+ await addChildMessage({
595
+ id: generateId(),
596
+ role: MessageRole.ASSISTANT,
597
+ seq: 0,
598
+ origin: 'local',
599
+ parts: [makeTextPart(partialContent)],
600
+ content: partialContent,
601
+ createdAt: nowISO(),
602
+ disabled: false,
603
+ pinned: false,
604
+ hidden: false,
605
+ agentRole: subagentType,
606
+ ...(partialReasoning ? { reasoning: partialReasoning } : {}),
607
+ metadata: {
608
+ stream_id: extra.streamId,
609
+ partial: true,
610
+ interrupted: true,
611
+ },
612
+ });
613
+ }
614
+ // 2. Маркер прерывания в историю дочерней сессии
615
+ const stopMarkerText = sm('task.stopped_by_user');
616
+ await addChildMessage({
617
+ id: generateId(),
618
+ role: MessageRole.INFO,
619
+ seq: 0,
620
+ origin: 'local',
621
+ parts: [makeTextPart(stopMarkerText)],
622
+ content: stopMarkerText,
623
+ createdAt: nowISO(),
624
+ disabled: false,
625
+ pinned: false,
626
+ hidden: false,
627
+ agentRole: subagentType,
628
+ metadata: {
629
+ stream_id: extra.streamId,
630
+ stopped_by: 'user',
631
+ interrupted: true,
632
+ i18n: i18nMeta('task.stopped_by_user'),
633
+ },
634
+ });
635
+ // 3. Финализация дочерней сессии
636
+ await finalizeChildSession();
637
+ // 4. tool_result родителю: маркер отмены + частичный текст + шаги + id сессии
638
+ const stepsCompleted = allSubagentToolCalls.length > 0
639
+ ? allSubagentToolCalls.map((t) => t.name).join(', ')
640
+ : 'none';
641
+ return {
642
+ content: [
643
+ '<task_result>',
644
+ `<summary>${description}</summary>`,
645
+ '<status>stopped_by_user</status>',
646
+ ...(childSessionId ? [`<child_session_id>${childSessionId}</child_session_id>`] : []),
647
+ ...(partialContent ? [`<partial_output>\n${partialContent}\n</partial_output>`] : []),
648
+ `<steps_completed>${stepsCompleted}</steps_completed>`,
649
+ sm('task.stopped_before_completion'),
650
+ '</task_result>',
651
+ ].join('\n'),
652
+ metadata: {
653
+ subagent_type: subagentType,
654
+ description: description,
655
+ context_mode: contextMode,
656
+ steps: iteration,
657
+ child_session_id: childSessionId,
658
+ stopped_by: 'user',
659
+ interrupted: true,
660
+ tool_calls: allSubagentToolCalls.length > 0 ? allSubagentToolCalls : undefined,
661
+ partial_output: partialContent || undefined,
662
+ },
663
+ };
664
+ };
665
+ while (iteration < effectiveMaxSteps) {
666
+ if (isAborted())
667
+ break;
668
+ let accumulatedContent = '';
669
+ let toolCalls = [];
670
+ let reasoning = '';
671
+ let lastPublishedReasoning = '';
672
+ let lastPublishedContentLen = 0;
673
+ // ─── LLM-вызов с retry (B1, аналог QueueProcessor) ──────────────
674
+ // Recoverable-ошибки (network/timeout/rate_limit/server_error) ретраятся
675
+ // до retryMaxAttempts попыток с backoff; unrecoverable (auth/
676
+ // model_not_found/context_length) — сразу финальная ошибка. Backoff-пауза
677
+ // спит порциями с проверкой отмены: «Стоп» прерывает ожидание мгновенно.
678
+ // Событие subagent.retry и паузы НЕ пишут ничего в историю дочерней
679
+ // сессии — мусорные сообщения не создаются (только финальный ERROR).
680
+ let llmFailure = null;
681
+ for (let attempt = 1; attempt <= retryMaxAttempts; attempt++) {
682
+ if (isAborted())
421
683
  break;
422
- let accumulatedContent = '';
423
- let toolCalls = [];
424
- let reasoning = '';
425
684
  try {
426
685
  for await (const chunk of effectiveProvider.chatAsync(messages, systemPrompt || undefined, tools, undefined, cancelToken)) {
427
- if (cancelToken.cancelled)
686
+ if (isAborted())
428
687
  break;
429
688
  if (chunk.isFinal) {
430
689
  accumulatedContent = chunk.content;
@@ -432,62 +691,148 @@ export const taskTool = {
432
691
  }
433
692
  else {
434
693
  accumulatedContent += chunk.content;
694
+ const chunkReasoning = chunk.reasoning;
695
+ if (chunkReasoning)
696
+ reasoning = chunkReasoning;
435
697
  }
436
698
  if (chunk.toolCalls && chunk.toolCalls.length > 0) {
437
699
  toolCalls = chunk.toolCalls;
438
700
  }
439
- }
440
- }
441
- catch (err) {
442
- return {
443
- content: `Ошибка выполнения сабагента "${params.subagent_type}": ${String(err)}`,
444
- isError: true,
445
- metadata: { subagent_type: params.subagent_type },
446
- };
447
- }
448
- if (cancelToken.cancelled)
449
- break;
450
- if (reasoning) {
451
- allSubagentReasoning.push({ text: reasoning, iteration });
452
- }
453
- if (!toolCalls || toolCalls.length === 0) {
454
- finalContent = accumulatedContent;
455
- if (reasoning)
456
- subagentReasoning = reasoning;
457
- // Сохранить финальный ответ ассистента в дочернюю сессию
458
- if (childSessionId && extra.sessionManager) {
459
- try {
460
- const assistantMsg = {
461
- id: generateId(),
462
- role: MessageRole.ASSISTANT,
463
- seq: 0,
464
- origin: 'local',
465
- parts: [makeTextPart(accumulatedContent)],
466
- content: accumulatedContent,
467
- createdAt: nowISO(),
468
- disabled: false,
469
- pinned: false,
470
- hidden: false,
471
- agentRole: params.subagent_type,
472
- reasoning: reasoning || undefined,
473
- };
474
- const updated = await extra.sessionManager.addMessage(childSessionId, assistantMsg);
475
- if (updated && extra.eventBus) {
476
- const idx = updated.messages.length - 1;
477
- await publishMessageAdded(extra.eventBus, extra.sessionId, childSessionId, updated.messages[idx], idx, updated);
701
+ // Стримим размышления/промежуточный текст саб-агента РОДИТЕЛЮ,
702
+ // чтобы в блоке `task` (по parent_call_id) они были видны во время работы.
703
+ if (publish) {
704
+ const liveReasoning = reasoning || '';
705
+ const liveContent = accumulatedContent || '';
706
+ if (liveReasoning !== lastPublishedReasoning || liveContent.length !== lastPublishedContentLen) {
707
+ publish('subagent.progress', {
708
+ stream_id: extra.streamId,
709
+ session_id: extra.sessionId,
710
+ parent_call_id: extra.callId,
711
+ reasoning: liveReasoning,
712
+ content: liveContent,
713
+ iteration,
714
+ });
715
+ lastPublishedReasoning = liveReasoning;
716
+ lastPublishedContentLen = liveContent.length;
478
717
  }
479
718
  }
480
- catch {
481
- // не фатально
482
- }
483
719
  }
720
+ llmFailure = null;
484
721
  break;
485
722
  }
486
- if (accumulatedContent) {
487
- allSubagentIntermediateTexts.push({ text: accumulatedContent, iteration });
723
+ catch (err) {
724
+ // Прерывание пользователем во время стрима (провайдер бросил
725
+ // AbortError) — не ошибка, обработается через finishAborted ниже.
726
+ if (isAborted())
727
+ break;
728
+ const rawError = err instanceof Error ? err.message : String(err);
729
+ const errorType = classifyLlmError(rawError);
730
+ if (!isLlmErrorRecoverable(err, rawError) || attempt >= retryMaxAttempts) {
731
+ // Unrecoverable или попытки исчерпаны — финальная ошибка
732
+ llmFailure = { error: err, errorType, attempts: attempt };
733
+ break;
734
+ }
735
+ const delay = retryDelaysMs[Math.min(attempt - 1, retryDelaysMs.length - 1)] ?? 0;
736
+ publish?.('subagent.retry', {
737
+ stream_id: extra.streamId,
738
+ session_id: extra.sessionId,
739
+ child_session_id: childSessionId,
740
+ parent_call_id: extra.callId,
741
+ attempt,
742
+ max_retries: retryMaxAttempts,
743
+ retry_in: delay,
744
+ error_type: errorType,
745
+ error: rawError,
746
+ });
747
+ if (await sleepAbortable(delay, isAborted))
748
+ break;
749
+ // Сброс частичного контекста неудавшейся попытки
750
+ accumulatedContent = '';
751
+ reasoning = '';
752
+ toolCalls = [];
753
+ lastPublishedReasoning = '';
754
+ lastPublishedContentLen = 0;
488
755
  }
489
- iteration++;
490
- // Сохранить ответ ассистента с tool calls в дочернюю сессию
756
+ }
757
+ if (llmFailure) {
758
+ // Ошибка провайдера внутри саб-агента (после retry-цикла): фиксируем
759
+ // в истории дочерней сессии, финализируем её и возвращаем родителю
760
+ // tool_result с описанием ошибки, счётчиком попыток и child_session_id.
761
+ const attempts = llmFailure.attempts;
762
+ const errParams = { type: subagentType, error: String(llmFailure.error) };
763
+ const errKey = attempts > 1
764
+ ? 'task.subagent_exec_error_after_retries'
765
+ : 'task.subagent_exec_error';
766
+ const errText = attempts > 1
767
+ ? sm(errKey, { ...errParams, attempts: String(attempts) })
768
+ : sm(errKey, errParams);
769
+ await addChildMessage({
770
+ id: generateId(),
771
+ role: MessageRole.ERROR,
772
+ seq: 0,
773
+ origin: 'local',
774
+ parts: [makeTextPart(errText)],
775
+ content: errText,
776
+ createdAt: nowISO(),
777
+ disabled: false,
778
+ pinned: false,
779
+ hidden: false,
780
+ agentRole: subagentType,
781
+ metadata: {
782
+ stream_id: extra.streamId,
783
+ error: true,
784
+ attempts,
785
+ max_retries: retryMaxAttempts,
786
+ error_type: llmFailure.errorType,
787
+ i18n: i18nMeta(errKey, attempts > 1 ? { ...errParams, attempts: String(attempts) } : errParams),
788
+ },
789
+ });
790
+ await finalizeChildSession();
791
+ return {
792
+ content: [
793
+ '<task_result>',
794
+ `<summary>${description}</summary>`,
795
+ '<status>error</status>',
796
+ ...(childSessionId ? [`<child_session_id>${childSessionId}</child_session_id>`] : []),
797
+ `<attempts>${attempts}</attempts>`,
798
+ errText,
799
+ '</task_result>',
800
+ ].join('\n'),
801
+ isError: true,
802
+ metadata: {
803
+ subagent_type: subagentType,
804
+ description: description,
805
+ context_mode: contextMode,
806
+ steps: iteration,
807
+ child_session_id: childSessionId,
808
+ error: true,
809
+ attempts,
810
+ max_retries: retryMaxAttempts,
811
+ error_type: llmFailure.errorType,
812
+ tool_calls: allSubagentToolCalls.length > 0 ? allSubagentToolCalls : undefined,
813
+ },
814
+ };
815
+ }
816
+ // Отмена (Стоп в родительской сессии): сохраняем частичный текст
817
+ // ТЕКУЩЕЙ итерации, ставим маркер прерывания в историю дочерней
818
+ // сессии, финализируем её (без статуса completed) и возвращаем
819
+ // родителю tool_result с маркером остановки, частичным ответом и
820
+ // списком выполненных шагов.
821
+ if (isAborted()) {
822
+ return await finishAborted(accumulatedContent, reasoning);
823
+ }
824
+ // Единая итерация для размышлений, промежуточного текста и вызовов
825
+ // инструментов этого шага — иначе в UI (и в дочерней сессии) ломается
826
+ // порядок: размышления идут на step N, а вызовы — на N+1.
827
+ const stepIteration = iteration;
828
+ if (reasoning) {
829
+ allSubagentReasoning.push({ text: reasoning, iteration: stepIteration });
830
+ }
831
+ if (!toolCalls || toolCalls.length === 0) {
832
+ finalContent = accumulatedContent;
833
+ if (reasoning)
834
+ subagentReasoning = reasoning;
835
+ // Сохранить финальный ответ ассистента в дочернюю сессию
491
836
  if (childSessionId && extra.sessionManager) {
492
837
  try {
493
838
  const assistantMsg = {
@@ -495,215 +840,237 @@ export const taskTool = {
495
840
  role: MessageRole.ASSISTANT,
496
841
  seq: 0,
497
842
  origin: 'local',
498
- parts: [makeTextPart(accumulatedContent || '')],
499
- content: accumulatedContent || '',
843
+ parts: [makeTextPart(accumulatedContent)],
844
+ content: accumulatedContent,
500
845
  createdAt: nowISO(),
501
846
  disabled: false,
502
847
  pinned: false,
503
848
  hidden: false,
504
- agentRole: params.subagent_type,
505
- reasoning: reasoning || undefined,
849
+ agentRole: subagentType,
850
+ // Все размышления сабагента (по итерациям), склеенные в поле
851
+ // reasoning — UI дочерней сессии показывает их блоками
852
+ // (ReasoningBlock разбивает по «\n\n---\n\n»). Без этого размышления
853
+ // остаются только в скрытых tool_chain-сообщениях и теряются.
854
+ reasoning: allSubagentReasoning.length > 0
855
+ ? allSubagentReasoning.map((r) => r.text).join('\n\n---\n\n')
856
+ : undefined,
857
+ // Как в _persistToolChain родителя: финальное сообщение несёт
858
+ // свёрнутую цепочку тулов, чтобы UI дочерней сессии отрисовал её
859
+ // так же аккуратно, как финал родительской.
860
+ metadata: {
861
+ stream_id: extra.streamId,
862
+ ...(allSubagentToolCalls.length > 0 ? { tool_results: allSubagentToolCalls } : {}),
863
+ ...(allSubagentIntermediateTexts.length > 0 ? { intermediate_text_blocks: allSubagentIntermediateTexts } : {}),
864
+ },
506
865
  };
507
866
  const updated = await extra.sessionManager.addMessage(childSessionId, assistantMsg);
508
867
  if (updated && extra.eventBus) {
509
868
  const idx = updated.messages.length - 1;
510
- await publishMessageAdded(extra.eventBus, extra.sessionId, childSessionId, updated.messages[idx], idx, updated);
869
+ await publishMessageAdded(extra.eventBus, parentSessionId, childSessionId, updated.messages[idx], idx, updated);
511
870
  }
512
871
  }
513
872
  catch {
514
873
  // не фатально
515
874
  }
516
875
  }
517
- messages.push({
518
- id: '',
519
- role: 'assistant',
520
- content: accumulatedContent || '',
521
- usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
522
- disabled: false,
523
- pinned: false,
524
- branchId: '',
525
- toolUse: toolCalls,
526
- });
527
- for (const tc of toolCalls) {
528
- const tcId = tc.id || `subcall_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
529
- const func = tc.function;
530
- if (!func?.name)
531
- continue;
532
- const toolName = func.name;
533
- let toolArgs;
534
- if (typeof func.arguments === 'string') {
535
- try {
536
- toolArgs = JSON.parse(func.arguments);
537
- }
538
- catch {
539
- toolArgs = {};
540
- }
876
+ break;
877
+ }
878
+ if (accumulatedContent) {
879
+ allSubagentIntermediateTexts.push({ text: accumulatedContent, iteration });
880
+ }
881
+ iteration++;
882
+ // Сохранить ответ ассистента с tool calls в дочернюю сессию
883
+ if (childSessionId && extra.sessionManager) {
884
+ try {
885
+ const assistantMsg = {
886
+ id: generateId(),
887
+ role: MessageRole.ASSISTANT,
888
+ seq: 0,
889
+ origin: 'local',
890
+ parts: [makeTextPart(accumulatedContent || '')],
891
+ content: accumulatedContent || '',
892
+ createdAt: nowISO(),
893
+ disabled: false,
894
+ pinned: false,
895
+ hidden: false,
896
+ agentRole: subagentType,
897
+ reasoning: reasoning || undefined,
898
+ // OpenAI-совместимая цепочка: assistant(toolCalls) → tool.
899
+ // tool_chain=true — UI дочерней сессии скрывает промежуточные
900
+ // сообщения (визуализация уже в финальном с tool_results).
901
+ toolCalls,
902
+ metadata: { stream_id: extra.streamId, tool_chain: true },
903
+ };
904
+ const updated = await extra.sessionManager.addMessage(childSessionId, assistantMsg);
905
+ if (updated && extra.eventBus) {
906
+ const idx = updated.messages.length - 1;
907
+ await publishMessageAdded(extra.eventBus, parentSessionId, childSessionId, updated.messages[idx], idx, updated);
541
908
  }
542
- else {
543
- toolArgs = func.arguments || {};
909
+ }
910
+ catch {
911
+ // не фатально
912
+ }
913
+ }
914
+ messages.push({
915
+ id: '',
916
+ role: 'assistant',
917
+ content: accumulatedContent || '',
918
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
919
+ disabled: false,
920
+ pinned: false,
921
+ branchId: '',
922
+ toolUse: toolCalls,
923
+ });
924
+ for (const tc of toolCalls) {
925
+ const tcId = tc.id || `subcall_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
926
+ const func = tc.function;
927
+ if (!func?.name)
928
+ continue;
929
+ const toolName = func.name;
930
+ let toolArgs;
931
+ if (typeof func.arguments === 'string') {
932
+ try {
933
+ toolArgs = JSON.parse(func.arguments);
544
934
  }
545
- const tcStartedAt = new Date().toISOString();
546
- const doomKey = `${toolName}:${JSON.stringify(toolArgs)}`;
547
- const doomCount = (doomTracker.get(doomKey) || 0) + 1;
548
- doomTracker.set(doomKey, doomCount);
549
- if (doomCount >= 3) {
550
- const doomMsg = {
551
- id: '', role: 'tool',
552
- content: `Ошибка: инструмент "${toolName}" вызван ${doomCount} раз(а) с одинаковыми аргументами. Цикл прерван.`,
553
- usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
554
- disabled: false, pinned: false, branchId: '',
555
- toolCallId: tcId,
556
- };
557
- messages.push(doomMsg);
558
- if (childSessionId && extra.sessionManager) {
559
- try {
560
- const toolResultMsg = {
561
- id: generateId(),
562
- role: MessageRole.TOOL,
563
- seq: 0,
564
- origin: 'local',
565
- parts: [makeTextPart(doomMsg.content)],
566
- content: doomMsg.content,
567
- createdAt: nowISO(),
568
- disabled: false,
569
- pinned: false,
570
- hidden: false,
571
- toolCallId: tcId,
572
- };
573
- const updated = await extra.sessionManager.addMessage(childSessionId, toolResultMsg);
574
- if (updated && extra.eventBus) {
575
- const idx = updated.messages.length - 1;
576
- await publishMessageAdded(extra.eventBus, extra.sessionId, childSessionId, updated.messages[idx], idx, updated);
577
- }
578
- }
579
- catch {
580
- // не фатально
935
+ catch {
936
+ toolArgs = {};
937
+ }
938
+ }
939
+ else {
940
+ toolArgs = func.arguments || {};
941
+ }
942
+ const tcStartedAt = new Date().toISOString();
943
+ const doomKey = `${toolName}:${JSON.stringify(toolArgs)}`;
944
+ const doomCount = (doomTracker.get(doomKey) || 0) + 1;
945
+ doomTracker.set(doomKey, doomCount);
946
+ if (doomCount >= 3) {
947
+ const doomMsg = {
948
+ id: '', role: 'tool',
949
+ content: sm('tool.doom_loop', { tool: toolName, count: String(doomCount) }),
950
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
951
+ disabled: false, pinned: false, branchId: '',
952
+ toolCallId: tcId,
953
+ };
954
+ messages.push(doomMsg);
955
+ if (childSessionId && extra.sessionManager) {
956
+ try {
957
+ const toolResultMsg = {
958
+ id: generateId(),
959
+ role: MessageRole.TOOL,
960
+ seq: 0,
961
+ origin: 'local',
962
+ parts: [makeTextPart(doomMsg.content)],
963
+ content: doomMsg.content,
964
+ createdAt: nowISO(),
965
+ disabled: false,
966
+ pinned: false,
967
+ hidden: false,
968
+ toolCallId: tcId,
969
+ metadata: {
970
+ stream_id: extra.streamId,
971
+ tool_chain: true,
972
+ tool_name: toolName,
973
+ args: toolArgs,
974
+ },
975
+ };
976
+ const updated = await extra.sessionManager.addMessage(childSessionId, toolResultMsg);
977
+ if (updated && extra.eventBus) {
978
+ const idx = updated.messages.length - 1;
979
+ await publishMessageAdded(extra.eventBus, parentSessionId, childSessionId, updated.messages[idx], idx, updated);
581
980
  }
582
981
  }
583
- continue;
982
+ catch {
983
+ // не фатально
984
+ }
584
985
  }
585
- publish?.('tool_call.started', {
586
- stream_id: extra.streamId,
587
- session_id: extra.sessionId,
588
- call_id: tcId,
589
- name: toolName,
590
- arguments: toolArgs,
591
- iteration,
592
- });
593
- const toolDef = extra.toolRegistry?.get(toolName);
594
- let toolResult;
595
- let subagentUserDenied = false;
596
- // AD-029: permission-check саб-агента — permissions дочерней сессии
597
- // (родитель + deny-дополнения) применяются к каждому tool_call.
598
- const permissionDecision = childEvaluator.check({
599
- toolName,
600
- params: toolArgs,
601
- workingDir: ctx.workingDir,
602
- capabilities: toolDef?.capabilities,
603
- sessionMode: childSessionMode,
604
- planMode: childPlanMode,
605
- planApproved: childPlanApproved,
606
- mcpToolSettings: childMcpToolSettings,
607
- });
608
- if (permissionDecision.action === 'deny') {
609
- toolResult = {
610
- content: `Permission denied: ${permissionDecision.reason}`,
611
- isError: true,
612
- metadata: {
613
- permission: {
614
- action: 'deny',
615
- reason: permissionDecision.reason,
616
- matched_rule: permissionDecision.matchedRule,
617
- },
986
+ continue;
987
+ }
988
+ publish?.('tool_call.started', {
989
+ stream_id: extra.streamId,
990
+ session_id: extra.sessionId,
991
+ call_id: tcId,
992
+ name: toolName,
993
+ arguments: toolArgs,
994
+ iteration: stepIteration,
995
+ // Вложенность внутренних вызовов саб-агента под блоком `task`
996
+ // в родительском стриме (UI строит дерево по parent_call_id).
997
+ parent_call_id: extra.callId,
998
+ });
999
+ const toolDef = extra.toolRegistry?.get(toolName);
1000
+ let toolResult;
1001
+ let subagentUserDenied = false;
1002
+ // AD-029: permission-check саб-агента — permissions дочерней сессии
1003
+ // (родитель + deny-дополнения) применяются к каждому tool_call.
1004
+ const permissionDecision = childEvaluator.check({
1005
+ toolName,
1006
+ params: toolArgs,
1007
+ workingDir: ctx.workingDir,
1008
+ capabilities: toolDef?.capabilities,
1009
+ sessionMode: childSessionMode,
1010
+ planMode: childPlanMode,
1011
+ planApproved: childPlanApproved,
1012
+ mcpToolSettings: childMcpToolSettings,
1013
+ shellPolicy: extra.shellPolicy ?? undefined,
1014
+ protectedPaths: extra.protectedPaths ?? undefined,
1015
+ protectedPathsEnabled: extra.protectedPathsEnabled,
1016
+ });
1017
+ if (permissionDecision.action === 'deny') {
1018
+ toolResult = {
1019
+ content: extra.contextConfig && permissionDecision.messageId
1020
+ ? (extra.contextConfig.getSecurityMessage(permissionDecision.messageId, 'en', permissionDecision.params)
1021
+ ?? `Permission denied: ${permissionDecision.reason}`)
1022
+ : `Permission denied: ${permissionDecision.reason}`,
1023
+ isError: true,
1024
+ metadata: {
1025
+ ...(permissionDecision.messageId
1026
+ ? { i18n: i18nMeta(`security.${permissionDecision.messageId.replaceAll('-', '_')}`, permissionDecision.params) }
1027
+ : { i18n: i18nMeta('tool.permission_denied_default') }),
1028
+ permission: {
1029
+ action: 'deny',
1030
+ reason: permissionDecision.reason,
1031
+ matched_rule: permissionDecision.matchedRule,
618
1032
  },
619
- };
620
- }
621
- else if (permissionDecision.action === 'ask') {
622
- // ask публикуется в родительскую сессию (extra.sessionId)
623
- // пользователь основной сессии видит запрос и отвечает.
624
- const approval = await childApprovalManager.waitForDecision(extra.sessionId, {
625
- approvalId: `approval_${tcId}`,
626
- callId: tcId,
627
- toolName,
628
- arguments: toolArgs,
629
- reason: permissionDecision.reason,
630
- }, derivedPermission.askTimeoutMs);
631
- if (approval.approved) {
632
- if (approval.once === false) {
633
- // «Разрешить в сессии» — allow-правило в permissions дочерней сессии
634
- derivedPermission.rules[toolName] = 'allow';
635
- if (childSessionId && extra.sessionManager) {
636
- try {
637
- const child = await extra.sessionManager.getSession(childSessionId);
638
- if (child) {
639
- child.settings.permission = { ...derivedPermission };
640
- await extra.sessionManager.updateSession(child);
641
- }
642
- }
643
- catch {
644
- // не фатально
1033
+ },
1034
+ };
1035
+ }
1036
+ else if (permissionDecision.action === 'ask') {
1037
+ // ask публикуется в родительскую сессию (extra.sessionId)
1038
+ // пользователь основной сессии видит запрос и отвечает.
1039
+ const approvalAskReason = extra.contextConfig && permissionDecision.messageId
1040
+ ? (extra.contextConfig.getSecurityMessage(permissionDecision.messageId, 'en', permissionDecision.params)
1041
+ ?? permissionDecision.reason)
1042
+ : permissionDecision.reason;
1043
+ const approval = await childApprovalManager.waitForDecision(extra.sessionId, {
1044
+ approvalId: `approval_${tcId}`,
1045
+ callId: tcId,
1046
+ toolName,
1047
+ arguments: toolArgs,
1048
+ reason: approvalAskReason,
1049
+ }, derivedPermission.askTimeoutMs);
1050
+ if (approval.approved) {
1051
+ if (approval.once === false) {
1052
+ // «Разрешить в сессии» — allow-правило в permissions дочерней сессии
1053
+ derivedPermission.rules[toolName] = 'allow';
1054
+ if (childSessionId && extra.sessionManager) {
1055
+ try {
1056
+ const child = await extra.sessionManager.getSession(childSessionId);
1057
+ if (child) {
1058
+ child.settings.permission = { ...derivedPermission };
1059
+ await extra.sessionManager.updateSession(child);
645
1060
  }
646
1061
  }
647
- }
648
- if (toolDef) {
649
- const parsed2 = toolDef.parameters.safeParse(toolArgs);
650
- if (!parsed2.success) {
651
- toolResult = {
652
- content: `Ошибка аргументов инструмента "${toolName}": ${parsed2.error.issues.map(i => `${i.path.join('.') || 'root'}: ${i.message}`).join('; ')}`,
653
- isError: true,
654
- };
655
- }
656
- else {
657
- try {
658
- toolResult = await toolDef.execute(parsed2.data, {
659
- ...ctx,
660
- abortSignal: ctx.abortSignal,
661
- extra: extra,
662
- });
663
- }
664
- catch (execError) {
665
- toolResult = {
666
- content: `Ошибка исполнения инструмента "${toolName}": ${String(execError)}`,
667
- isError: true,
668
- };
669
- }
1062
+ catch {
1063
+ // не фатально
670
1064
  }
671
1065
  }
672
- else {
673
- toolResult = {
674
- content: `Инструмент "${toolName}" не найден`,
675
- isError: true,
676
- };
677
- }
678
1066
  }
679
- else {
680
- const why = approval.timeout
681
- ? 'истекло время ожидания подтверждения'
682
- : approval.noSubscribers
683
- ? 'нет подключённого пользователя для подтверждения'
684
- : 'отклонено пользователем';
685
- toolResult = {
686
- content: `Permission denied: ${permissionDecision.reason} (${why})`,
687
- isError: true,
688
- metadata: {
689
- permission: {
690
- action: 'deny',
691
- reason: permissionDecision.reason,
692
- matched_rule: permissionDecision.matchedRule,
693
- },
694
- },
695
- };
696
- subagentUserDenied = !approval.timeout && !approval.noSubscribers;
697
- }
698
- }
699
- else {
700
- // allow — обычное выполнение
701
1067
  if (toolDef) {
702
1068
  const parsed2 = toolDef.parameters.safeParse(toolArgs);
703
1069
  if (!parsed2.success) {
704
1070
  toolResult = {
705
- content: `Ошибка аргументов инструмента "${toolName}": ${parsed2.error.issues.map(i => `${i.path.join('.') || 'root'}: ${i.message}`).join('; ')}`,
1071
+ content: sm('tool.args_error', { tool: toolName, issues: parsed2.error.issues.map(i => `${i.path.join('.') || 'root'}: ${i.message}`).join('; ') }),
706
1072
  isError: true,
1073
+ metadata: { i18n: i18nMeta('tool.args_error', { tool: toolName }) },
707
1074
  };
708
1075
  }
709
1076
  else {
@@ -716,229 +1083,289 @@ export const taskTool = {
716
1083
  }
717
1084
  catch (execError) {
718
1085
  toolResult = {
719
- content: `Ошибка исполнения инструмента "${toolName}": ${String(execError)}`,
1086
+ content: sm('tool.exec_error', { tool: toolName, error: String(execError) }),
720
1087
  isError: true,
1088
+ metadata: { i18n: i18nMeta('tool.exec_error', { tool: toolName }) },
721
1089
  };
722
1090
  }
723
1091
  }
724
1092
  }
725
1093
  else {
726
1094
  toolResult = {
727
- content: `Инструмент "${toolName}" не найден`,
1095
+ content: sm('tool.not_found', { tool: toolName }),
728
1096
  isError: true,
1097
+ metadata: { i18n: i18nMeta('tool.not_found', { tool: toolName }) },
729
1098
  };
730
1099
  }
731
1100
  }
732
- publish?.('tool_call.result', {
733
- stream_id: extra.streamId,
734
- session_id: extra.sessionId,
735
- call_id: tcId,
736
- name: toolName,
737
- result: toolResult,
738
- iteration,
739
- });
740
- allSubagentToolCalls.push({
741
- name: toolName,
742
- args: toolArgs,
743
- id: tcId,
744
- result: { content: toolResult.content, isError: toolResult.isError },
745
- started_at: tcStartedAt,
746
- completed_at: new Date().toISOString(),
747
- iteration,
748
- });
749
- const toolResultLLMMsg = {
750
- id: '', role: 'tool',
751
- content: toolResult.content,
752
- usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
753
- disabled: false, pinned: false, branchId: '',
754
- toolCallId: tcId,
755
- };
756
- messages.push(toolResultLLMMsg);
757
- // Сохранить tool_result в дочернюю сессию
758
- if (childSessionId && extra.sessionManager) {
759
- try {
760
- const toolResultMsg = {
761
- id: generateId(),
762
- role: MessageRole.TOOL,
763
- seq: 0,
764
- origin: 'local',
765
- parts: [makeTextPart(toolResult.content)],
766
- content: toolResult.content,
767
- createdAt: nowISO(),
768
- disabled: false,
769
- pinned: false,
770
- hidden: false,
771
- toolCallId: tcId,
1101
+ else {
1102
+ const why = approval.timeout
1103
+ ? sm('permission.ask_timeout')
1104
+ : approval.noSubscribers
1105
+ ? sm('permission.ask_no_subscribers')
1106
+ : sm('permission.ask_denied');
1107
+ const baseDeny = extra.contextConfig && permissionDecision.messageId
1108
+ ? (extra.contextConfig.getSecurityMessage(permissionDecision.messageId, 'en', permissionDecision.params)
1109
+ ?? `Permission denied: ${permissionDecision.reason}`)
1110
+ : `Permission denied: ${permissionDecision.reason}`;
1111
+ toolResult = {
1112
+ content: `${baseDeny} (${why})`,
1113
+ isError: true,
1114
+ metadata: {
1115
+ ...(permissionDecision.messageId
1116
+ ? { i18n: i18nMeta(`security.${permissionDecision.messageId.replaceAll('-', '_')}`, permissionDecision.params) }
1117
+ : {}),
1118
+ permission: {
1119
+ action: 'deny',
1120
+ reason: permissionDecision.reason,
1121
+ matched_rule: permissionDecision.matchedRule,
1122
+ },
1123
+ },
1124
+ };
1125
+ subagentUserDenied = !approval.timeout && !approval.noSubscribers;
1126
+ }
1127
+ }
1128
+ else {
1129
+ // allow обычное выполнение
1130
+ if (toolDef) {
1131
+ const parsed2 = toolDef.parameters.safeParse(toolArgs);
1132
+ if (!parsed2.success) {
1133
+ toolResult = {
1134
+ content: sm('tool.args_error', { tool: toolName, issues: parsed2.error.issues.map(i => `${i.path.join('.') || 'root'}: ${i.message}`).join('; ') }),
1135
+ isError: true,
772
1136
  };
773
- const updated = await extra.sessionManager.addMessage(childSessionId, toolResultMsg);
774
- if (updated && extra.eventBus) {
775
- const idx = updated.messages.length - 1;
776
- await publishMessageAdded(extra.eventBus, extra.sessionId, childSessionId, updated.messages[idx], idx, updated);
777
- }
778
1137
  }
779
- catch {
780
- // не фатально
1138
+ else {
1139
+ try {
1140
+ toolResult = await toolDef.execute(parsed2.data, {
1141
+ ...ctx,
1142
+ abortSignal: ctx.abortSignal,
1143
+ extra: extra,
1144
+ });
1145
+ }
1146
+ catch (execError) {
1147
+ toolResult = {
1148
+ content: sm('tool.exec_error', { tool: toolName, error: String(execError) }),
1149
+ isError: true,
1150
+ metadata: { i18n: i18nMeta('tool.exec_error', { tool: toolName }) },
1151
+ };
1152
+ }
781
1153
  }
782
1154
  }
783
- // AD-029: пользователь отклонил ask саб-агента — прекращаем выполнение.
784
- if (subagentUserDenied) {
785
- subagentDenied = true;
786
- break;
1155
+ else {
1156
+ toolResult = {
1157
+ content: sm('tool.not_found', { tool: toolName }),
1158
+ isError: true,
1159
+ metadata: { i18n: i18nMeta('tool.not_found', { tool: toolName }) },
1160
+ };
787
1161
  }
788
1162
  }
789
- publish?.('tool_call.loop', {
1163
+ publish?.('tool_call.result', {
790
1164
  stream_id: extra.streamId,
791
1165
  session_id: extra.sessionId,
792
- iteration,
793
- total_tool_calls: toolCalls.length,
1166
+ call_id: tcId,
1167
+ name: toolName,
1168
+ result: toolResult,
1169
+ iteration: stepIteration,
1170
+ parent_call_id: extra.callId,
1171
+ });
1172
+ allSubagentToolCalls.push({
1173
+ name: toolName,
1174
+ args: toolArgs,
1175
+ id: tcId,
1176
+ result: { content: toolResult.content, isError: toolResult.isError },
1177
+ started_at: tcStartedAt,
1178
+ completed_at: new Date().toISOString(),
1179
+ iteration: stepIteration,
794
1180
  });
795
- if (subagentDenied)
1181
+ const toolResultLLMMsg = {
1182
+ id: '', role: 'tool',
1183
+ content: toolResult.content,
1184
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
1185
+ disabled: false, pinned: false, branchId: '',
1186
+ toolCallId: tcId,
1187
+ };
1188
+ messages.push(toolResultLLMMsg);
1189
+ // Сохранить tool_result в дочернюю сессию
1190
+ if (childSessionId && extra.sessionManager) {
1191
+ try {
1192
+ const toolResultMsg = {
1193
+ id: generateId(),
1194
+ role: MessageRole.TOOL,
1195
+ seq: 0,
1196
+ origin: 'local',
1197
+ parts: [makeTextPart(toolResult.content)],
1198
+ content: toolResult.content,
1199
+ createdAt: nowISO(),
1200
+ disabled: false,
1201
+ pinned: false,
1202
+ hidden: false,
1203
+ toolCallId: tcId,
1204
+ metadata: {
1205
+ stream_id: extra.streamId,
1206
+ tool_chain: true,
1207
+ tool_name: toolName,
1208
+ args: toolArgs,
1209
+ },
1210
+ };
1211
+ const updated = await extra.sessionManager.addMessage(childSessionId, toolResultMsg);
1212
+ if (updated && extra.eventBus) {
1213
+ const idx = updated.messages.length - 1;
1214
+ await publishMessageAdded(extra.eventBus, parentSessionId, childSessionId, updated.messages[idx], idx, updated);
1215
+ }
1216
+ }
1217
+ catch {
1218
+ // не фатально
1219
+ }
1220
+ }
1221
+ // AD-029: пользователь отклонил ask саб-агента — прекращаем выполнение.
1222
+ if (subagentUserDenied) {
1223
+ subagentDenied = true;
796
1224
  break;
1225
+ }
797
1226
  }
798
- if (subagentDenied && !finalContent) {
799
- finalContent = 'Обработка остановлена: пользователь отклонил запрос на выполнение инструмента';
800
- }
801
- const output = finalContent || '(пустой ответ сабагента)';
802
- // ─── Quality check результата саб-агента (AD-034 Фаза 4) ─────
803
- let qaPass;
804
- let qaReason;
805
- let qualityOutput = output;
806
- const escalationCfg = extra.routerConfig?.escalation;
807
- const sessionEscalation = extra.parentSessionSettings?.routingEscalation;
808
- const escalationEnabled = sessionEscalation?.enabled ?? escalationCfg?.enabled ?? false;
809
- if (extra.modelRouter && escalationEnabled && finalContent && routingDecisionInfo) {
810
- try {
811
- const qaResult = await extra.modelRouter.checkQuality(finalContent, {
812
- providerName: routingDecisionInfo.providerName,
813
- modelName: routingDecisionInfo.modelName,
814
- reason: routingDecisionInfo.strategy,
815
- strategy: routingDecisionInfo.strategy,
816
- }, extra.parentSessionSettings);
817
- qaPass = qaResult.pass;
818
- qaReason = qaResult.reason;
819
- // Пометить QA-информацию в дочерней сессии
820
- if (childSessionId && extra.sessionManager) {
821
- try {
822
- const childSession = await extra.sessionManager.getSession(childSessionId);
823
- if (childSession) {
824
- const lastAssistantMsg = [...childSession.messages].reverse()
825
- .find(m => m.role === MessageRole.ASSISTANT);
826
- if (lastAssistantMsg) {
827
- lastAssistantMsg.qa_pass = qaResult.pass;
828
- lastAssistantMsg.qa_reason = qaResult.reason;
829
- await extra.sessionManager.updateSession(childSession);
830
- }
1227
+ publish?.('tool_call.loop', {
1228
+ stream_id: extra.streamId,
1229
+ session_id: extra.sessionId,
1230
+ iteration: stepIteration,
1231
+ total_tool_calls: toolCalls.length,
1232
+ });
1233
+ if (subagentDenied)
1234
+ break;
1235
+ }
1236
+ // Отмена до/после цикла (например сигнал уже абортирован на входе или
1237
+ // пришёл во время исполнения тулов саб-агента) — тот же протокол
1238
+ // прерывания, что и при отмене во время стрима.
1239
+ if (isAborted()) {
1240
+ return await finishAborted('', '');
1241
+ }
1242
+ if (subagentDenied && !finalContent) {
1243
+ finalContent = sm('task.stopped_by_deny');
1244
+ }
1245
+ const output = finalContent || sm('task.empty_response');
1246
+ // ─── Quality check результата саб-агента (AD-034 Фаза 4) ─────
1247
+ let qaPass;
1248
+ let qaReason;
1249
+ let qualityOutput = output;
1250
+ const escalationCfg = extra.routerConfig?.escalation;
1251
+ const sessionEscalation = extra.parentSessionSettings?.routingEscalation;
1252
+ const escalationEnabled = sessionEscalation?.enabled ?? escalationCfg?.enabled ?? false;
1253
+ if (extra.modelRouter && escalationEnabled && finalContent && routingDecisionInfo) {
1254
+ try {
1255
+ const qaResult = await extra.modelRouter.checkQuality(finalContent, {
1256
+ providerName: routingDecisionInfo.providerName,
1257
+ modelName: routingDecisionInfo.modelName,
1258
+ reason: routingDecisionInfo.strategy,
1259
+ strategy: routingDecisionInfo.strategy,
1260
+ }, extra.parentSessionSettings);
1261
+ qaPass = qaResult.pass;
1262
+ qaReason = qaResult.reason;
1263
+ // Пометить QA-информацию в дочерней сессии
1264
+ if (childSessionId && extra.sessionManager) {
1265
+ try {
1266
+ const childSession = await extra.sessionManager.getSession(childSessionId);
1267
+ if (childSession) {
1268
+ const lastAssistantMsg = [...childSession.messages].reverse()
1269
+ .find(m => m.role === MessageRole.ASSISTANT);
1270
+ if (lastAssistantMsg) {
1271
+ lastAssistantMsg.qa_pass = qaResult.pass;
1272
+ lastAssistantMsg.qa_reason = qaResult.reason;
1273
+ await extra.sessionManager.updateSession(childSession);
831
1274
  }
832
1275
  }
833
- catch {
834
- // не фатально
835
- }
836
1276
  }
837
- // Эскалация на fallback-модель при FAIL
838
- if (!qaResult.pass) {
839
- const fallbackProvider = sessionEscalation?.fallbackProvider || escalationCfg?.fallbackProvider;
840
- const fallbackModel = sessionEscalation?.fallbackModel || escalationCfg?.fallbackModel;
841
- if (fallbackProvider && fallbackModel && extra.modelRouter) {
842
- const fbDecision = {
843
- providerName: fallbackProvider,
844
- modelName: fallbackModel,
845
- reason: 'escalation_fallback',
846
- strategy: 'default',
847
- };
848
- const fbEffectiveProvider = extra.modelRouter.getProvider(fbDecision);
849
- if (fbEffectiveProvider) {
850
- try {
851
- // Перевызываем с fallback-моделью (только последний user-запрос)
852
- const fbMessages = [messages[0]];
853
- // Добавляем только user-сообщение без tool-результатов
854
- const userMsg = messages.find(m => m.role === 'user');
855
- if (userMsg)
856
- fbMessages.push(userMsg);
857
- const fbResponse = await fbEffectiveProvider.chat(fbMessages, systemPrompt || undefined, undefined, false);
858
- if (fbResponse.content) {
859
- finalContent = fbResponse.content;
860
- qualityOutput = fbResponse.content;
861
- qaPass = true;
862
- qaReason = `Эскалация на ${fallbackProvider}/${fallbackModel}: ${qaResult.reason}`;
863
- routingDecisionInfo = {
864
- providerName: fallbackProvider,
865
- modelName: fallbackModel,
866
- strategy: 'escalation_fallback',
867
- };
868
- }
869
- }
870
- catch {
871
- // fallback не удался, оставляем оригинальный ответ
1277
+ catch {
1278
+ // не фатально
1279
+ }
1280
+ }
1281
+ // Эскалация на fallback-модель при FAIL
1282
+ if (!qaResult.pass) {
1283
+ const fallbackProvider = sessionEscalation?.fallbackProvider || escalationCfg?.fallbackProvider;
1284
+ const fallbackModel = sessionEscalation?.fallbackModel || escalationCfg?.fallbackModel;
1285
+ if (fallbackProvider && fallbackModel && extra.modelRouter) {
1286
+ const fbDecision = {
1287
+ providerName: fallbackProvider,
1288
+ modelName: fallbackModel,
1289
+ reason: 'escalation_fallback',
1290
+ strategy: 'default',
1291
+ };
1292
+ const fbEffectiveProvider = extra.modelRouter.getProvider(fbDecision);
1293
+ if (fbEffectiveProvider) {
1294
+ try {
1295
+ // Перевызываем с fallback-моделью (только последний user-запрос)
1296
+ const fbMessages = [messages[0]];
1297
+ // Добавляем только user-сообщение без tool-результатов
1298
+ const userMsg = messages.find(m => m.role === 'user');
1299
+ if (userMsg)
1300
+ fbMessages.push(userMsg);
1301
+ const fbResponse = await fbEffectiveProvider.chat(fbMessages, systemPrompt || undefined, undefined, false);
1302
+ if (fbResponse.content) {
1303
+ finalContent = fbResponse.content;
1304
+ qualityOutput = fbResponse.content;
1305
+ qaPass = true;
1306
+ qaReason = sm('router.escalation', { provider: fallbackProvider, model: fallbackModel, reason: qaResult.reason });
1307
+ routingDecisionInfo = {
1308
+ providerName: fallbackProvider,
1309
+ modelName: fallbackModel,
1310
+ strategy: 'escalation_fallback',
1311
+ };
872
1312
  }
873
1313
  }
1314
+ catch {
1315
+ // fallback не удался, оставляем оригинальный ответ
1316
+ }
874
1317
  }
875
1318
  }
876
1319
  }
877
- catch {
878
- // QA не удался, не блокируем
879
- }
880
1320
  }
881
- // Финализировать дочернюю сессию — обновить TSM
882
- if (childSessionId && extra.sessionManager) {
883
- try {
884
- const childSession = await extra.sessionManager.getSession(childSessionId);
885
- if (childSession) {
886
- childSession.status.state = TsmState.DONE;
887
- childSession.updatedAt = nowISO();
888
- await extra.sessionManager.updateSession(childSession);
889
- if (extra.eventBus) {
890
- extra.eventBus.publish(childSessionId, 'session.updated', {
891
- session_id: childSessionId,
892
- status: { state: TsmState.DONE },
893
- });
894
- extra.eventBus.publish(extra.sessionId, 'child.session.updated', {
895
- session: serializeSession(childSession),
896
- });
897
- }
898
- }
899
- }
900
- catch {
901
- // не фатально
902
- }
1321
+ catch {
1322
+ // QA не удался, не блокируем
903
1323
  }
904
- // ─── Формирование результата tool_result ──────────────────────
905
- const qualityBlock = qaPass !== undefined
906
- ? `<quality pass="${qaPass}" reason="${qaReason || ''}"/>\n`
907
- : '';
908
- const routedByBlock = routingDecisionInfo && routingDecisionInfo.strategy !== 'default'
909
- ? `<routed_by strategy="${routingDecisionInfo.strategy}" provider="${routingDecisionInfo.providerName}" model="${routingDecisionInfo.modelName}"/>\n`
910
- : '';
911
- const qaWarningText = qaPass === false
912
- ? `\n\n⚠️ Ответ не прошёл контроль качества: ${qaReason || 'неизвестная причина'}`
913
- : '';
914
- return {
915
- content: `<task_result>\n<summary>${params.description}</summary>\n${qualityBlock}${routedByBlock}\n${qualityOutput}${qaWarningText}\n</task_result>`,
916
- metadata: {
917
- subagent_type: params.subagent_type,
918
- description: params.description,
919
- context_mode: contextMode,
920
- steps: iteration,
921
- child_session_id: childSessionId,
922
- reasoning: subagentReasoning || undefined,
923
- reasoning_blocks: allSubagentReasoning.length > 0 ? allSubagentReasoning : undefined,
924
- intermediate_text_blocks: allSubagentIntermediateTexts.length > 0 ? allSubagentIntermediateTexts : undefined,
925
- tool_calls: allSubagentToolCalls.length > 0 ? allSubagentToolCalls : undefined,
926
- final_content: finalContent || undefined,
927
- qa_pass: qaPass,
928
- qa_reason: qaReason || undefined,
929
- routing_strategy: routingDecisionInfo?.strategy,
930
- routing_provider: routingDecisionInfo?.providerName,
931
- routing_model: routingDecisionInfo?.modelName,
932
- },
933
- };
934
1324
  }
935
- catch (err) {
936
- return {
937
- content: `Ошибка выполнения сабагента "${params.subagent_type}": ${String(err)}`,
938
- isError: true,
939
- metadata: { subagent_type: params.subagent_type },
940
- };
941
- }
942
- },
943
- };
1325
+ // Финализировать дочернюю сессию
1326
+ await finalizeChildSession();
1327
+ // ─── Формирование результата tool_result ──────────────────────
1328
+ const qualityBlock = qaPass !== undefined
1329
+ ? `<quality pass="${qaPass}" reason="${qaReason || ''}"/>\n`
1330
+ : '';
1331
+ const routedByBlock = routingDecisionInfo && routingDecisionInfo.strategy !== 'default'
1332
+ ? `<routed_by strategy="${routingDecisionInfo.strategy}" provider="${routingDecisionInfo.providerName}" model="${routingDecisionInfo.modelName}"/>\n`
1333
+ : '';
1334
+ const qaWarningText = qaPass === false
1335
+ ? `\n\n⚠️ Response failed the quality check: ${qaReason || 'unknown reason'}`
1336
+ : '';
1337
+ // ID дочерней сессии в content — родительская модель может прочитать
1338
+ // историю саб-агента тулом read_session_reference.
1339
+ const childSessionBlock = childSessionId
1340
+ ? `<child_session_id>${childSessionId}</child_session_id>\n`
1341
+ : '';
1342
+ return {
1343
+ content: `<task_result>\n<summary>${description}</summary>\n${qualityBlock}${routedByBlock}${childSessionBlock}\n${qualityOutput}${qaWarningText}\n</task_result>`,
1344
+ metadata: {
1345
+ subagent_type: subagentType,
1346
+ description: description,
1347
+ context_mode: contextMode,
1348
+ steps: iteration,
1349
+ child_session_id: childSessionId,
1350
+ reasoning: subagentReasoning || undefined,
1351
+ reasoning_blocks: allSubagentReasoning.length > 0 ? allSubagentReasoning : undefined,
1352
+ intermediate_text_blocks: allSubagentIntermediateTexts.length > 0 ? allSubagentIntermediateTexts : undefined,
1353
+ tool_calls: allSubagentToolCalls.length > 0 ? allSubagentToolCalls : undefined,
1354
+ final_content: finalContent || undefined,
1355
+ qa_pass: qaPass,
1356
+ qa_reason: qaReason || undefined,
1357
+ routing_strategy: routingDecisionInfo?.strategy,
1358
+ routing_provider: routingDecisionInfo?.providerName,
1359
+ routing_model: routingDecisionInfo?.modelName,
1360
+ },
1361
+ };
1362
+ }
1363
+ catch (err) {
1364
+ return {
1365
+ content: sm('task.subagent_exec_error', { type: subagentType, error: String(err) }),
1366
+ isError: true,
1367
+ metadata: { subagent_type: subagentType },
1368
+ };
1369
+ }
1370
+ }
944
1371
  //# sourceMappingURL=task.js.map