@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
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Общая классификация ошибок LLM-провайдеров.
3
+ *
4
+ * Вынесено из ChatOrchestrator (_classifyError / _isRecoverable), чтобы
5
+ * retry-цикл саб-агента (task.ts / task-resume.ts) использовал ту же
6
+ * таксономию, что и QueueProcessor основной сессии:
7
+ * recoverable — network_error, timeout, rate_limit, server_error, unknown
8
+ * unrecoverable — auth_error, model_not_found, context_length, provider_not_found
9
+ */
10
+ import { ContextLengthExceededError } from '../providers/exceptions.js';
11
+ /**
12
+ * Классификация ошибки провайдера по тексту. Совпадает с
13
+ * ChatOrchestrator._classifyError (оркестратор делегирует сюда).
14
+ */
15
+ export function classifyLlmError(errMsg) {
16
+ const lower = errMsg.toLowerCase();
17
+ if (lower.includes('rate') || lower.includes('limit') || lower.includes('429'))
18
+ return 'rate_limit';
19
+ if (lower.includes('abort') || lower.includes('operation was aborted'))
20
+ return 'timeout';
21
+ if (lower.includes('timeout') || lower.includes('timed out'))
22
+ return 'timeout';
23
+ if (lower.includes('fetch failed') || lower.includes('network') || lower.includes('econnrefused') || lower.includes('enotfound') || lower.includes('eai_again'))
24
+ return 'network_error';
25
+ if (lower.includes('502') || lower.includes('503') || lower.includes('bad gateway'))
26
+ return 'server_error';
27
+ if (lower.includes('context length') || lower.includes('too long'))
28
+ return 'context_length';
29
+ if (lower.includes('401') || lower.includes('403') || lower.includes('auth'))
30
+ return 'auth_error';
31
+ if (lower.includes('model') && (lower.includes('not found') || lower.includes('invalid')))
32
+ return 'model_not_found';
33
+ return 'unknown';
34
+ }
35
+ /** Типы ошибок, при которых повтор не имеет смысла. */
36
+ const UNRECOVERABLE_TYPES = [
37
+ 'auth_error', 'model_not_found', 'context_length', 'provider_not_found',
38
+ 'validation',
39
+ ];
40
+ /**
41
+ * Подстроки, однозначно указывающие на невосполнимую ошибку.
42
+ *
43
+ * Единая точка истины — объединение из трёх ранее расходившихся списков:
44
+ * - llm-error.ts (auth/model/key/подстрока 401): model not found,
45
+ * model_not_found, does not exist, invalid model, unknown model,
46
+ * invalid api key, incorrect api key, 401, authentication, unauthorized
47
+ * - queue/processor.ts._isRecoverable (валидация/десериализация/type):
48
+ * missing field, failed to deserialize, invalid type, unknown field,
49
+ * bad request, validation, err_invalid_arg_type, cannot read properties,
50
+ * is not a function, is not defined
51
+ */
52
+ const UNRECOVERABLE_PATTERNS = [
53
+ 'model not found', 'model_not_found', 'does not exist',
54
+ 'invalid model', 'unknown model', 'invalid api key',
55
+ 'incorrect api key', '401', 'authentication', 'unauthorized',
56
+ 'missing field', 'failed to deserialize', 'invalid type',
57
+ 'unknown field', 'bad request', 'validation',
58
+ 'err_invalid_arg_type', 'cannot read properties',
59
+ 'is not a function', 'is not defined',
60
+ ];
61
+ /**
62
+ * Политика ретрая LLM-провайдеров — единая для основной сессии
63
+ * (QueueProcessor/оркестратор) и саб-агента (task.ts).
64
+ */
65
+ export const LLM_MAX_ATTEMPTS = 3;
66
+ /** Backoff между попытками, мс (index = попытка - 1). */
67
+ export const LLM_RETRY_DELAYS_MS = [5000, 15000, 30000];
68
+ /**
69
+ * Восполняемая ли ошибка провайдера (можно ретраить): network/timeout/
70
+ * rate_limit/server_error — да; auth/model_not_found/context_length —
71
+ * нет. Приоритеты:
72
+ * 1. ContextLengthExceededError (по классу) — невосполнима.
73
+ * 2. Явно переданный `errorType` из UNRECOVERABLE_TYPES — невосполнима.
74
+ * 3. Иначе — по подстрокам UNRECOVERABLE_PATTERNS в
75
+ * `errMsg ?? error.message ?? String(error)`.
76
+ */
77
+ export function isLlmErrorRecoverable(error, errMsg, errorType) {
78
+ if (error instanceof ContextLengthExceededError)
79
+ return false;
80
+ if (errorType && UNRECOVERABLE_TYPES.includes(errorType))
81
+ return false;
82
+ const raw = errMsg ?? (error instanceof Error ? error.message : String(error));
83
+ const lower = raw.toLowerCase();
84
+ return !UNRECOVERABLE_PATTERNS.some((p) => lower.includes(p));
85
+ }
86
+ //# sourceMappingURL=llm-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-error.js","sourceRoot":"","sources":["../../src/engine/llm-error.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=llm-error.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-error.test.d.ts","sourceRoot":"","sources":["../../src/engine/llm-error.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,101 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { classifyLlmError, isLlmErrorRecoverable, LLM_MAX_ATTEMPTS, LLM_RETRY_DELAYS_MS } from './llm-error.js';
3
+ import { ContextLengthExceededError } from '../providers/exceptions.js';
4
+ describe('classifyLlmError — единая таксономия типов', () => {
5
+ it('классифицирует recoverable-ошибки', () => {
6
+ expect(classifyLlmError('rate limit exceeded')).toBe('rate_limit');
7
+ expect(classifyLlmError('429 Too Many Requests')).toBe('rate_limit');
8
+ expect(classifyLlmError('timeout')).toBe('timeout');
9
+ expect(classifyLlmError('fetch failed: ECONNREFUSED')).toBe('network_error');
10
+ expect(classifyLlmError('502 Bad Gateway')).toBe('server_error');
11
+ expect(classifyLlmError('503 Service Unavailable')).toBe('server_error');
12
+ });
13
+ it('классифицирует unrecoverable-ошибки', () => {
14
+ expect(classifyLlmError('401 Unauthorized')).toBe('auth_error');
15
+ expect(classifyLlmError('model not found')).toBe('model_not_found');
16
+ expect(classifyLlmError('context length exceeded')).toBe('context_length');
17
+ expect(classifyLlmError('неизвестное')).toBe('unknown');
18
+ });
19
+ });
20
+ describe('isLlmErrorRecoverable — объединённый набор паттернов', () => {
21
+ // Представительные строки из бывшего списка llm-error.ts (auth/model/key).
22
+ it.each([
23
+ 'model not found',
24
+ 'model_not_found',
25
+ 'model does not exist',
26
+ 'invalid model selected',
27
+ 'unknown model',
28
+ 'invalid api key',
29
+ 'incorrect api key',
30
+ 'HTTP 401',
31
+ 'authentication required',
32
+ 'unauthorized',
33
+ ])('паттерн llm-error.ts: %s → unrecoverable (false)', (msg) => {
34
+ expect(isLlmErrorRecoverable(new Error(msg))).toBe(false);
35
+ });
36
+ // Представительные строки из бывшего списка queue/processor.ts._isRecoverable
37
+ // (валидация/десериализация/type).
38
+ it.each([
39
+ 'missing field: name',
40
+ 'failed to deserialize',
41
+ 'invalid type: expected string',
42
+ 'unknown field',
43
+ 'bad request',
44
+ 'validation error',
45
+ 'err_invalid_arg_type',
46
+ 'cannot read properties of undefined',
47
+ 'x is not a function',
48
+ 'y is not defined',
49
+ ])('паттерн processor.ts: %s → unrecoverable (false)', (msg) => {
50
+ expect(isLlmErrorRecoverable(undefined, msg)).toBe(false);
51
+ });
52
+ // Recoverable-строки должны остаться восполняемыми.
53
+ it.each([
54
+ 'fetch failed: ECONNREFUSED',
55
+ '503 Service Unavailable',
56
+ 'rate limit exceeded',
57
+ 'AbortError: This operation was aborted',
58
+ 'request timed out',
59
+ 'connection reset',
60
+ 'неизвестная ошибка',
61
+ ])('recoverable-строка: %s → true', (msg) => {
62
+ expect(isLlmErrorRecoverable(new Error(msg), msg)).toBe(true);
63
+ });
64
+ it('использует error.message при отсутствии errMsg', () => {
65
+ expect(isLlmErrorRecoverable(new Error('invalid api key'))).toBe(false);
66
+ expect(isLlmErrorRecoverable(new Error('fetch failed'))).toBe(true);
67
+ });
68
+ it('использует String(error) для не-Error значений', () => {
69
+ expect(isLlmErrorRecoverable('401 Unauthorized')).toBe(false);
70
+ expect(isLlmErrorRecoverable('timeout')).toBe(true);
71
+ });
72
+ });
73
+ describe('isLlmErrorRecoverable — приоритет errorType', () => {
74
+ // errorType из UNRECOVERABLE_TYPES -> false, даже если текст их не содержит.
75
+ it.each(['context_length', 'auth_error', 'model_not_found', 'provider_not_found', 'validation'])('errorType=%s → false даже без совпадения по тексту', (type) => {
76
+ expect(isLlmErrorRecoverable(new Error('argle bargle'), 'argle bargle', type)).toBe(false);
77
+ });
78
+ // recoverable-типы в errorType → true (текст не содержит паттернов).
79
+ it.each(['timeout', 'network_error', 'rate_limit', 'server_error', 'unknown'])('errorType=%s → true', (type) => {
80
+ expect(isLlmErrorRecoverable(new Error('argle bargle'), 'argle bargle', type)).toBe(true);
81
+ });
82
+ it('errorType=context_length побеждает даже при recoverable-тексте', () => {
83
+ expect(isLlmErrorRecoverable(new Error('fetch failed'), 'fetch failed', 'context_length')).toBe(false);
84
+ });
85
+ });
86
+ describe('isLlmErrorRecoverable — ContextLengthExceededError', () => {
87
+ it('возвращает false по классу ошибки (не по тексту)', () => {
88
+ // Текст «too long» НЕ входит в UNRECOVERABLE_PATTERNS — решает именно класс.
89
+ expect(isLlmErrorRecoverable(new ContextLengthExceededError('too long'))).toBe(false);
90
+ expect(isLlmErrorRecoverable(new ContextLengthExceededError('Контекстное окно превышено'))).toBe(false);
91
+ });
92
+ });
93
+ describe('Политика ретрая — единые константы', () => {
94
+ it('LLM_MAX_ATTEMPTS = 3', () => {
95
+ expect(LLM_MAX_ATTEMPTS).toBe(3);
96
+ });
97
+ it('LLM_RETRY_DELAYS_MS = [5000, 15000, 30000]', () => {
98
+ expect(LLM_RETRY_DELAYS_MS).toEqual([5000, 15000, 30000]);
99
+ });
100
+ });
101
+ //# sourceMappingURL=llm-error.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-error.test.js","sourceRoot":"","sources":["../../src/engine/llm-error.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=orchestrator-deny-abort.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator-deny-abort.test.d.ts","sourceRoot":"","sources":["../../src/engine/orchestrator-deny-abort.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,414 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdtemp, rm } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { z } from 'zod';
6
+ import { ChatOrchestrator } from './orchestrator.js';
7
+ import { SessionManager } from '../sessions/manager.js';
8
+ import { FileStore } from '../storage/file-store.js';
9
+ import { EventBus } from '../events/bus.js';
10
+ import { createToolRegistry } from './tools/index.js';
11
+ import { ContextConfig } from './context/config.js';
12
+ import { ProviderFactory, BaseProvider } from '../providers/base.js';
13
+ import { generateId, MessageRole, nowISO, makeTextPart, ToolCapability } from '@itphx/synth-shared';
14
+ function makeCtxBuilder() {
15
+ return {
16
+ buildSystemPrompt: async () => 'system prompt',
17
+ };
18
+ }
19
+ function makeMessage() {
20
+ return {
21
+ id: generateId(),
22
+ role: MessageRole.USER,
23
+ seq: 0,
24
+ origin: 'local',
25
+ parts: [makeTextPart('выполни задачу')],
26
+ content: 'выполни задачу',
27
+ createdAt: nowISO(),
28
+ disabled: false,
29
+ pinned: false,
30
+ hidden: false,
31
+ author: 'user',
32
+ metadata: { stream_id: generateId() },
33
+ };
34
+ }
35
+ /**
36
+ * Провайдер для теста deny + пустой финальный ответ:
37
+ * вызовы 0..2 — execute_shell с опасной командой (deny по системной
38
+ * shell-политике definitions/shell-policy.json), вызов 3 — пустой
39
+ * контент без toolCalls (модель «сдалась» после серии deny).
40
+ */
41
+ class MockDenyLoopProvider extends BaseProvider {
42
+ static calls = 0;
43
+ _next() {
44
+ if (MockDenyLoopProvider.calls < 3) {
45
+ const n = MockDenyLoopProvider.calls;
46
+ return {
47
+ content: '',
48
+ toolCalls: [{
49
+ id: `call_d${n}`,
50
+ function: { name: 'execute_shell', arguments: JSON.stringify({ command: 'rm -rf /' }) },
51
+ }],
52
+ };
53
+ }
54
+ return { content: '', toolCalls: undefined };
55
+ }
56
+ async chat(_messages, _systemPrompt, _tools) {
57
+ const next = this._next();
58
+ MockDenyLoopProvider.calls++;
59
+ return { content: next.content, model: this.model, usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 }, toolCalls: next.toolCalls };
60
+ }
61
+ async *chatAsync(_messages, _systemPrompt, _tools, _debugCollector, _cancellationToken) {
62
+ const next = this._next();
63
+ MockDenyLoopProvider.calls++;
64
+ yield { content: next.content, isFinal: true, toolCalls: next.toolCalls, model: this.model, usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 } };
65
+ }
66
+ getProviderName() {
67
+ return 'mockdenyloop';
68
+ }
69
+ }
70
+ /**
71
+ * Провайдер для теста abort:
72
+ * вызов 0 — завершённая итерация: текст + execute_shell (deny);
73
+ * вызов 1 — медленный стрим текста, прерывается abort'ом.
74
+ */
75
+ class MockAbortProvider extends BaseProvider {
76
+ static calls = 0;
77
+ async chat(_messages, _systemPrompt, _tools) {
78
+ return { content: 'x', model: this.model, usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 } };
79
+ }
80
+ async *chatAsync(_messages, _systemPrompt, _tools, _debugCollector, ct) {
81
+ const n = MockAbortProvider.calls++;
82
+ if (n === 0) {
83
+ yield {
84
+ content: 'Шаг 1: анализ команды', isFinal: true,
85
+ toolCalls: [{ id: 'call_a0', function: { name: 'execute_shell', arguments: JSON.stringify({ command: 'rm -rf /' }) } }],
86
+ model: this.model, usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
87
+ };
88
+ return;
89
+ }
90
+ const parts = ['Полный', ' частичный', ' ответ', ' — модель', ' уже', ' написала', ' это'];
91
+ for (const p of parts) {
92
+ if (ct?.cancelled)
93
+ return;
94
+ yield { content: p, isFinal: false, model: this.model, usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 } };
95
+ await new Promise((r) => setTimeout(r, 10));
96
+ }
97
+ yield { content: 'Конец', isFinal: true, model: this.model, usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 } };
98
+ }
99
+ getProviderName() {
100
+ return 'mockabort';
101
+ }
102
+ }
103
+ /** Провайдер: 3 одинаковых вызова read_file (НЕ deny), затем текст — для проверки сохранения doom loop. */
104
+ class MockDoomProvider extends BaseProvider {
105
+ static calls = 0;
106
+ _next() {
107
+ if (MockDoomProvider.calls < 3) {
108
+ const n = MockDoomProvider.calls;
109
+ return {
110
+ content: '',
111
+ toolCalls: [{
112
+ id: `call_r${n}`,
113
+ function: { name: 'read_file', arguments: JSON.stringify({ file_path: 'x.txt' }) },
114
+ }],
115
+ };
116
+ }
117
+ return { content: 'Готово' };
118
+ }
119
+ async chat(_messages, _systemPrompt, _tools) {
120
+ const next = this._next();
121
+ MockDoomProvider.calls++;
122
+ return { content: next.content, model: this.model, usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 }, toolCalls: next.toolCalls };
123
+ }
124
+ async *chatAsync(_messages, _systemPrompt, _tools, _debugCollector, _cancellationToken) {
125
+ const next = this._next();
126
+ MockDoomProvider.calls++;
127
+ yield { content: next.content, isFinal: true, toolCalls: next.toolCalls, model: this.model, usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 } };
128
+ }
129
+ getProviderName() {
130
+ return 'mockdoom';
131
+ }
132
+ }
133
+ /** Провайдер: всегда возвращает пустой контент без toolCalls (модель «не ответила»). */
134
+ class MockEmptyProvider extends BaseProvider {
135
+ async chat(_messages, _systemPrompt, _tools) {
136
+ return { content: '', model: this.model, usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 } };
137
+ }
138
+ async *chatAsync(_messages, _systemPrompt, _tools, _debugCollector, _cancellationToken) {
139
+ yield { content: '', isFinal: true, model: this.model, usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 } };
140
+ }
141
+ getProviderName() {
142
+ return 'mockempty';
143
+ }
144
+ }
145
+ async function makeHarness(providerName) {
146
+ const tmpDir = await mkdtemp(join(tmpdir(), 'synth-deny-abort-'));
147
+ const store = new FileStore({ dataDir: tmpDir });
148
+ const sessionManager = new SessionManager(store);
149
+ const eventBus = new EventBus();
150
+ const orchestrator = new ChatOrchestrator({
151
+ sessionManager,
152
+ eventBus,
153
+ contextBuilder: makeCtxBuilder(),
154
+ providerLoader: (name) => {
155
+ return { name, type: providerName, baseUrl: '', apiKey: '', defaultModel: 'm' };
156
+ },
157
+ toolRegistry: createToolRegistry(),
158
+ permissionConfig: { rules: {}, askTimeoutMs: 1000 },
159
+ defaultWorkingDir: tmpDir,
160
+ contextConfig: new ContextConfig({ userDataDir: join(tmpDir, 'context') }),
161
+ });
162
+ const session = await sessionManager.createSession({
163
+ title: 'DenyAbort',
164
+ settings: { provider: providerName, model: 'm', systemPrompt: 'test', agentRole: 'developer' },
165
+ });
166
+ const msg = makeMessage();
167
+ await sessionManager.addMessage(session.id, msg);
168
+ return { orchestrator, sessionManager, eventBus, tmpDir, sessionId: session.id };
169
+ }
170
+ async function run(h, signal, publishChunk) {
171
+ const processMessage = h.orchestrator.createProcessMessage();
172
+ const fresh = (await h.sessionManager.getSession(h.sessionId));
173
+ const userMsg = fresh.messages.find((m) => m.role === MessageRole.USER);
174
+ return processMessage(fresh, userMsg, signal, publishChunk);
175
+ }
176
+ describe('ChatOrchestrator: deny × doom loop + abort (баги фикса)', () => {
177
+ beforeEach(() => {
178
+ ProviderFactory.register('mockdenyloop', MockDenyLoopProvider);
179
+ ProviderFactory.register('mockabort', MockAbortProvider);
180
+ ProviderFactory.register('mockdoom', MockDoomProvider);
181
+ ProviderFactory.register('mockempty', MockEmptyProvider);
182
+ MockDenyLoopProvider.calls = 0;
183
+ MockAbortProvider.calls = 0;
184
+ MockDoomProvider.calls = 0;
185
+ });
186
+ afterEach(async () => {
187
+ ProviderFactory['_providers'].delete('mockdenyloop');
188
+ ProviderFactory['_providers'].delete('mockabort');
189
+ ProviderFactory['_providers'].delete('mockdoom');
190
+ ProviderFactory['_providers'].delete('mockempty');
191
+ });
192
+ it('deny 3+ раза: каждый deny даёт tool_call.result с объяснением политики, без «Цикл прерван»; пустой ответ модели подставлен', async () => {
193
+ const h = await makeHarness('mockdenyloop');
194
+ const chunks = [];
195
+ const result = await run(h, new AbortController().signal, (event, data) => chunks.push({ event, data }));
196
+ expect(result.error).toBeUndefined();
197
+ // Все 3 deny-вызова публикуют tool_call.result (в т.ч. 3-й — раньше его глушил doom loop)
198
+ const denyResults = chunks
199
+ .filter((c) => c.event === 'tool_call.result')
200
+ .map((c) => c.data.result);
201
+ expect(denyResults).toHaveLength(3);
202
+ for (const r of denyResults) {
203
+ expect(r.isError).toBe(true);
204
+ expect(r.content).toContain('Permission denied');
205
+ // Инструкция модели для deny
206
+ expect(r.content).toContain('The action is prohibited by the security policy');
207
+ // Никаких «Цикл прерван» без объяснения
208
+ expect(r.content).not.toContain('Цикл прерван');
209
+ }
210
+ // tool_call.started публикуется и для 3-го вызова
211
+ const starts = chunks.filter((c) => c.event === 'tool_call.started');
212
+ expect(starts).toHaveLength(3);
213
+ // Пустой финальный ответ модели подставлен
214
+ const done = chunks.find((c) => c.event === 'stream.done');
215
+ expect(done.data.content).toBe('Could not proceed: the action was blocked by the security policy');
216
+ // В истории нет пустого assistant-сообщения
217
+ const session = (await h.sessionManager.getSession(h.sessionId));
218
+ const assistant = [...session.messages].reverse().find((m) => m.role === MessageRole.ASSISTANT && !m.metadata?.tool_chain);
219
+ expect(assistant).toBeDefined();
220
+ expect(assistant.content.length).toBeGreaterThan(0);
221
+ expect(assistant.content).toContain('Could not proceed');
222
+ await rm(h.tmpDir, { recursive: true, force: true });
223
+ });
224
+ it('abort во время генерации после deny: в истории полный текст всех итераций + tool_results с deny', async () => {
225
+ const h = await makeHarness('mockabort');
226
+ const ac = new AbortController();
227
+ const chunks = [];
228
+ let textChunks = 0;
229
+ const promise = run(h, ac.signal, (event, data) => {
230
+ chunks.push({ event, data });
231
+ // После 2-го текстового чанка незавершённой итерации — стоп
232
+ if (event === 'stream.chunk' && data.iteration === 1) {
233
+ textChunks++;
234
+ if (textChunks >= 2)
235
+ setTimeout(() => ac.abort(), 5);
236
+ }
237
+ });
238
+ const result = await promise;
239
+ expect(result.error).toBeUndefined();
240
+ // stream.cancelled несёт полный накопленный текст (завершённая + текущая итерация)
241
+ const cancelled = chunks.find((c) => c.event === 'stream.cancelled');
242
+ const cancelledContent = cancelled.data.content;
243
+ expect(cancelledContent).toContain('Шаг 1: анализ команды');
244
+ expect(cancelledContent).toContain('Полный');
245
+ // Финальное assistant-сообщение содержит полный текст (не обрывок последней итерации)
246
+ const session = (await h.sessionManager.getSession(h.sessionId));
247
+ const assistant = [...session.messages].reverse().find((m) => m.role === MessageRole.ASSISTANT && !m.metadata?.tool_chain);
248
+ expect(assistant).toBeDefined();
249
+ expect(assistant.content).toContain('Шаг 1: анализ команды');
250
+ expect(assistant.content).toContain('Полный');
251
+ expect(assistant.content).not.toContain('Конец');
252
+ // deny сохранился в tool_results финального сообщения
253
+ const meta = assistant.metadata;
254
+ const toolResults = meta.tool_results;
255
+ expect(toolResults).toBeDefined();
256
+ expect(toolResults.length).toBeGreaterThan(0);
257
+ expect(toolResults[0].name).toBe('execute_shell');
258
+ expect(toolResults[0].result?.isError).toBe(true);
259
+ expect(toolResults[0].result?.content).toContain('Permission denied');
260
+ // При отмене blocks НЕ дублируются: они уже вошли в content (иначе UI
261
+ // режима 'bottom' склеил бы их с content повторно)
262
+ expect(meta.intermediate_text_blocks).toBeUndefined();
263
+ await rm(h.tmpDir, { recursive: true, force: true });
264
+ });
265
+ it('doom loop для НЕ-deny повторяющихся вызовов сохранён: 3-й одинаковый вызов не публикует tool_call.result', async () => {
266
+ const h = await makeHarness('mockdoom');
267
+ const chunks = [];
268
+ const result = await run(h, new AbortController().signal, (event, data) => chunks.push({ event, data }));
269
+ expect(result.error).toBeUndefined();
270
+ // Первые два вызова read_file выполнились (tool_call.result), третий заглушён doom loop
271
+ const toolResults = chunks.filter((c) => c.event === 'tool_call.result');
272
+ expect(toolResults).toHaveLength(2);
273
+ // Провайдер вызван 4 раза: 3 tool-итерации + финальный текст
274
+ expect(MockDoomProvider.calls).toBe(4);
275
+ // Нормальное завершение текстом
276
+ const done = chunks.find((c) => c.event === 'stream.done');
277
+ expect(done.data.content).toBe('Готово');
278
+ await rm(h.tmpDir, { recursive: true, force: true });
279
+ });
280
+ it('пустой ответ модели без deny — подставляется общий fallback, в истории нет пустого assistant-сообщения', async () => {
281
+ const h = await makeHarness('mockempty');
282
+ const chunks = [];
283
+ const result = await run(h, new AbortController().signal, (event, data) => chunks.push({ event, data }));
284
+ expect(result.error).toBeUndefined();
285
+ const done = chunks.find((c) => c.event === 'stream.done');
286
+ expect(done.data.content).toBe('Could not complete the request: the model response was empty');
287
+ const session = (await h.sessionManager.getSession(h.sessionId));
288
+ const assistant = [...session.messages].reverse().find((m) => m.role === MessageRole.ASSISTANT);
289
+ expect(assistant).toBeDefined();
290
+ expect(assistant.content).toBe('Could not complete the request: the model response was empty');
291
+ await rm(h.tmpDir, { recursive: true, force: true });
292
+ });
293
+ });
294
+ /** Тул, который «работает», пока его не прервут через ctx.abortSignal. */
295
+ function makeSlowAbortableTool() {
296
+ return {
297
+ name: 'slow_tool',
298
+ description: 'тестовый долгий тул — завершается при отмене',
299
+ parameters: z.object({}),
300
+ capabilities: [ToolCapability.RUNTIME],
301
+ async execute(_args, ctx) {
302
+ await new Promise((resolve) => {
303
+ if (ctx.abortSignal?.aborted) {
304
+ resolve();
305
+ return;
306
+ }
307
+ ctx.abortSignal?.addEventListener('abort', () => resolve(), { once: true });
308
+ });
309
+ return { content: 'slow_tool: прерван отменой' };
310
+ },
311
+ };
312
+ }
313
+ /**
314
+ * Провайдер для теста отмены ВО ВРЕМЯ исполнения тула:
315
+ * вызов 0 — текст + slow_tool; вызов 1 — не должен произойти
316
+ * (если баг вернулся, провайдер повиснет до отмены — тест это детектирует).
317
+ */
318
+ class MockToolCancelProvider extends BaseProvider {
319
+ static calls = 0;
320
+ async chat(_messages, _systemPrompt, _tools) {
321
+ return { content: '', model: this.model, usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 } };
322
+ }
323
+ async *chatAsync(_messages, _systemPrompt, _tools, _debugCollector, ct) {
324
+ const n = MockToolCancelProvider.calls++;
325
+ if (n === 0) {
326
+ yield {
327
+ content: 'Запускаю долгий тул', isFinal: true,
328
+ toolCalls: [{ id: 'call_slow', function: { name: 'slow_tool', arguments: '{}' } }],
329
+ model: this.model, usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
330
+ };
331
+ return;
332
+ }
333
+ // Второй итерации быть не должно: до её начала отмена уже зафиксирована.
334
+ // Если баг вернулся — цикл родителя дойдёт сюда и провайдер повиснет.
335
+ while (!ct?.cancelled) {
336
+ await new Promise((r) => setTimeout(r, 10));
337
+ }
338
+ return;
339
+ }
340
+ getProviderName() {
341
+ return 'mocktoolcancel';
342
+ }
343
+ }
344
+ describe('ChatOrchestrator: отмена во время исполнения тула', () => {
345
+ beforeEach(() => {
346
+ ProviderFactory.register('mocktoolcancel', MockToolCancelProvider);
347
+ MockToolCancelProvider.calls = 0;
348
+ });
349
+ afterEach(async () => {
350
+ ProviderFactory['_providers'].delete('mocktoolcancel');
351
+ });
352
+ it('abort во время тулa: публикуется stream.cancelled (не «ответ модели пуст» через stream.done), tool-chain сохранён', async () => {
353
+ const tmpDir = await mkdtemp(join(tmpdir(), 'synth-tool-cancel-'));
354
+ const store = new FileStore({ dataDir: tmpDir });
355
+ const sessionManager = new SessionManager(store);
356
+ const eventBus = new EventBus();
357
+ const toolRegistry = createToolRegistry();
358
+ toolRegistry.register(makeSlowAbortableTool());
359
+ const orchestrator = new ChatOrchestrator({
360
+ sessionManager,
361
+ eventBus,
362
+ contextBuilder: makeCtxBuilder(),
363
+ providerLoader: (name) => {
364
+ return { name, type: 'mocktoolcancel', baseUrl: '', apiKey: '', defaultModel: 'm' };
365
+ },
366
+ toolRegistry,
367
+ permissionConfig: { rules: {}, askTimeoutMs: 1000 },
368
+ defaultWorkingDir: tmpDir,
369
+ contextConfig: new ContextConfig({ userDataDir: join(tmpDir, 'context') }),
370
+ });
371
+ const session = await sessionManager.createSession({
372
+ title: 'ToolCancel',
373
+ settings: { provider: 'mocktoolcancel', model: 'm', systemPrompt: 'test', agentRole: 'developer' },
374
+ });
375
+ const msg = makeMessage();
376
+ await sessionManager.addMessage(session.id, msg);
377
+ const ac = new AbortController();
378
+ const chunks = [];
379
+ const processMessage = orchestrator.createProcessMessage();
380
+ const fresh = (await sessionManager.getSession(session.id));
381
+ const userMsg = fresh.messages.find((m) => m.role === MessageRole.USER);
382
+ const promise = processMessage(fresh, userMsg, ac.signal, (event, data) => {
383
+ chunks.push({ event, data });
384
+ // Тул запущен — жмём «Стоп»
385
+ if (event === 'tool_call.started') {
386
+ setTimeout(() => ac.abort(), 10);
387
+ }
388
+ });
389
+ await promise;
390
+ // Сабагент-тул прерван, вторая LLM-итерация не началась
391
+ expect(MockToolCancelProvider.calls).toBe(1);
392
+ // Завершение — как отмена: stream.cancelled, БЕЗ stream.done с «ответ модели пуст»
393
+ const cancelled = chunks.find((c) => c.event === 'stream.cancelled');
394
+ expect(cancelled).toBeDefined();
395
+ expect(cancelled.data.stream_id).toBe(msg.metadata.stream_id);
396
+ const done = chunks.find((c) => c.event === 'stream.done');
397
+ expect(done).toBeUndefined();
398
+ // Результат тулa опубликован (UI видит, чем тул завершился)
399
+ const toolResult = chunks.find((c) => c.event === 'tool_call.result');
400
+ expect(toolResult).toBeDefined();
401
+ expect(toolResult.data.result.content)
402
+ .toContain('прерван отменой');
403
+ // История: tool-chain сохранён (assistant с toolCalls + tool), без фейкового финала
404
+ const updated = (await sessionManager.getSession(session.id));
405
+ const toolMsg = updated.messages.find((m) => m.role === MessageRole.TOOL);
406
+ expect(toolMsg).toBeDefined();
407
+ expect(toolMsg.content).toContain('прерван отменой');
408
+ const cancelledAssistant = [...updated.messages].reverse().find((m) => m.role === MessageRole.ASSISTANT && !m.metadata?.tool_chain);
409
+ expect(cancelledAssistant).toBeDefined();
410
+ expect(cancelledAssistant.metadata?.tool_results).toBeDefined();
411
+ await rm(tmpDir, { recursive: true, force: true });
412
+ });
413
+ });
414
+ //# sourceMappingURL=orchestrator-deny-abort.test.js.map