@epoch-ai/cli 2.2.4 → 2.2.5

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 (711) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +18 -179
  3. package/.artifacts/unit/junit.xml +0 -2823
  4. package/.project-map/backups/20260530_223453/.project-map.json +0 -90101
  5. package/.project-map/backups/20260530_223507/.project-map.json +0 -90101
  6. package/.project-map/backups/20260530_223512/.project-map.json +0 -90101
  7. package/.project-map/backups/20260530_223512/map.toon +0 -666
  8. package/.project-map/backups/20260530_223516/.project-map.json +0 -90101
  9. package/.project-map/backups/20260530_223516/map.toon +0 -666
  10. package/.project-map/backups/20260530_223520/.project-map.json +0 -90101
  11. package/.project-map/backups/20260530_223520/map.toon +0 -666
  12. package/AGENTS.md +0 -47
  13. package/BUN_SHELL_MIGRATION_PLAN.md +0 -136
  14. package/Dockerfile +0 -18
  15. package/README.md +0 -15
  16. package/bunfig.toml +0 -7
  17. package/drizzle.config.ts +0 -10
  18. package/git +0 -0
  19. package/migration/20260127222353_familiar_lady_ursula/migration.sql +0 -90
  20. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +0 -796
  21. package/migration/20260211171708_add_project_commands/migration.sql +0 -1
  22. package/migration/20260211171708_add_project_commands/snapshot.json +0 -806
  23. package/migration/20260213144116_wakeful_the_professor/migration.sql +0 -11
  24. package/migration/20260213144116_wakeful_the_professor/snapshot.json +0 -897
  25. package/migration/20260225215848_workspace/migration.sql +0 -7
  26. package/migration/20260225215848_workspace/snapshot.json +0 -959
  27. package/migration/20260227213759_add_session_workspace_id/migration.sql +0 -2
  28. package/migration/20260227213759_add_session_workspace_id/snapshot.json +0 -983
  29. package/migration/20260228203230_blue_harpoon/migration.sql +0 -17
  30. package/migration/20260228203230_blue_harpoon/snapshot.json +0 -1102
  31. package/migration/20260303231226_add_workspace_fields/migration.sql +0 -5
  32. package/migration/20260303231226_add_workspace_fields/snapshot.json +0 -1013
  33. package/migration/20260309230000_move_org_to_state/migration.sql +0 -3
  34. package/migration/20260309230000_move_org_to_state/snapshot.json +0 -1156
  35. package/migration/20260312043431_session_message_cursor/migration.sql +0 -4
  36. package/migration/20260312043431_session_message_cursor/snapshot.json +0 -1168
  37. package/migration/20260323234822_events/migration.sql +0 -13
  38. package/migration/20260323234822_events/snapshot.json +0 -1271
  39. package/migration/20260418092949_add_yolo_to_session/migration.sql +0 -2
  40. package/migration/20260418092949_add_yolo_to_session/snapshot.json +0 -1199
  41. package/migration/20260419120000_add_intervention_to_session/migration.sql +0 -2
  42. package/parsers-config.ts +0 -290
  43. package/script/build-node.ts +0 -71
  44. package/script/build.ts +0 -255
  45. package/script/check-migrations.ts +0 -16
  46. package/script/fix-node-pty.ts +0 -28
  47. package/script/publish.ts +0 -184
  48. package/script/schema.ts +0 -63
  49. package/script/seed-e2e.ts +0 -60
  50. package/script/upgrade-opentui.ts +0 -64
  51. package/specs/effect-migration.md +0 -310
  52. package/specs/tui-plugins.md +0 -436
  53. package/specs/v2.md +0 -14
  54. package/src/account/account.sql.ts +0 -39
  55. package/src/account/index.ts +0 -465
  56. package/src/account/repo.ts +0 -163
  57. package/src/account/schema.ts +0 -91
  58. package/src/acp/README.md +0 -174
  59. package/src/acp/agent.ts +0 -1847
  60. package/src/acp/session.ts +0 -116
  61. package/src/acp/types.ts +0 -24
  62. package/src/agent/agent.ts +0 -445
  63. package/src/agent/generate.txt +0 -75
  64. package/src/agent/prompt/compaction.txt +0 -15
  65. package/src/agent/prompt/explore.txt +0 -9
  66. package/src/agent/prompt/summary.txt +0 -11
  67. package/src/agent/prompt/title.txt +0 -44
  68. package/src/auth/index.ts +0 -110
  69. package/src/bus/bus-event.ts +0 -40
  70. package/src/bus/global.ts +0 -10
  71. package/src/bus/index.ts +0 -232
  72. package/src/cli/bootstrap.ts +0 -17
  73. package/src/cli/cmd/account.ts +0 -257
  74. package/src/cli/cmd/acp.ts +0 -70
  75. package/src/cli/cmd/agent.ts +0 -245
  76. package/src/cli/cmd/cmd.ts +0 -7
  77. package/src/cli/cmd/db.ts +0 -119
  78. package/src/cli/cmd/debug/agent.ts +0 -167
  79. package/src/cli/cmd/debug/config.ts +0 -16
  80. package/src/cli/cmd/debug/file.ts +0 -97
  81. package/src/cli/cmd/debug/index.ts +0 -48
  82. package/src/cli/cmd/debug/lsp.ts +0 -53
  83. package/src/cli/cmd/debug/ripgrep.ts +0 -87
  84. package/src/cli/cmd/debug/scrap.ts +0 -16
  85. package/src/cli/cmd/debug/skill.ts +0 -16
  86. package/src/cli/cmd/debug/snapshot.ts +0 -52
  87. package/src/cli/cmd/export.ts +0 -89
  88. package/src/cli/cmd/generate.ts +0 -38
  89. package/src/cli/cmd/github.ts +0 -1639
  90. package/src/cli/cmd/import.ts +0 -169
  91. package/src/cli/cmd/mcp.ts +0 -754
  92. package/src/cli/cmd/models.ts +0 -78
  93. package/src/cli/cmd/plug.ts +0 -233
  94. package/src/cli/cmd/pr.ts +0 -127
  95. package/src/cli/cmd/providers.ts +0 -478
  96. package/src/cli/cmd/run.ts +0 -681
  97. package/src/cli/cmd/serve.ts +0 -24
  98. package/src/cli/cmd/session.ts +0 -159
  99. package/src/cli/cmd/stats.ts +0 -410
  100. package/src/cli/cmd/tui/app.tsx +0 -945
  101. package/src/cli/cmd/tui/attach.ts +0 -88
  102. package/src/cli/cmd/tui/component/border.tsx +0 -21
  103. package/src/cli/cmd/tui/component/dialog-agent.tsx +0 -31
  104. package/src/cli/cmd/tui/component/dialog-command.tsx +0 -171
  105. package/src/cli/cmd/tui/component/dialog-console-org.tsx +0 -103
  106. package/src/cli/cmd/tui/component/dialog-mcp.tsx +0 -86
  107. package/src/cli/cmd/tui/component/dialog-model.tsx +0 -190
  108. package/src/cli/cmd/tui/component/dialog-provider.tsx +0 -364
  109. package/src/cli/cmd/tui/component/dialog-session-list.tsx +0 -108
  110. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +0 -31
  111. package/src/cli/cmd/tui/component/dialog-skill.tsx +0 -36
  112. package/src/cli/cmd/tui/component/dialog-stash.tsx +0 -87
  113. package/src/cli/cmd/tui/component/dialog-status.tsx +0 -168
  114. package/src/cli/cmd/tui/component/dialog-tag.tsx +0 -44
  115. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +0 -50
  116. package/src/cli/cmd/tui/component/dialog-variant.tsx +0 -39
  117. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +0 -320
  118. package/src/cli/cmd/tui/component/error-component.tsx +0 -92
  119. package/src/cli/cmd/tui/component/logo.tsx +0 -85
  120. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +0 -14
  121. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +0 -672
  122. package/src/cli/cmd/tui/component/prompt/frecency.tsx +0 -90
  123. package/src/cli/cmd/tui/component/prompt/history.tsx +0 -109
  124. package/src/cli/cmd/tui/component/prompt/index.tsx +0 -1336
  125. package/src/cli/cmd/tui/component/prompt/part.ts +0 -16
  126. package/src/cli/cmd/tui/component/prompt/stash.tsx +0 -101
  127. package/src/cli/cmd/tui/component/spinner.tsx +0 -24
  128. package/src/cli/cmd/tui/component/startup-loading.tsx +0 -63
  129. package/src/cli/cmd/tui/component/textarea-keybindings.ts +0 -73
  130. package/src/cli/cmd/tui/component/todo-item.tsx +0 -32
  131. package/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx +0 -151
  132. package/src/cli/cmd/tui/context/args.tsx +0 -15
  133. package/src/cli/cmd/tui/context/directory.ts +0 -13
  134. package/src/cli/cmd/tui/context/exit.tsx +0 -60
  135. package/src/cli/cmd/tui/context/helper.tsx +0 -25
  136. package/src/cli/cmd/tui/context/keybind.tsx +0 -105
  137. package/src/cli/cmd/tui/context/kv.tsx +0 -52
  138. package/src/cli/cmd/tui/context/local.tsx +0 -456
  139. package/src/cli/cmd/tui/context/plugin-keybinds.ts +0 -41
  140. package/src/cli/cmd/tui/context/prompt.tsx +0 -18
  141. package/src/cli/cmd/tui/context/route.tsx +0 -52
  142. package/src/cli/cmd/tui/context/sdk.tsx +0 -115
  143. package/src/cli/cmd/tui/context/sync.tsx +0 -516
  144. package/src/cli/cmd/tui/context/theme/aura.json +0 -69
  145. package/src/cli/cmd/tui/context/theme/ayu.json +0 -80
  146. package/src/cli/cmd/tui/context/theme/carbonfox.json +0 -248
  147. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +0 -233
  148. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +0 -233
  149. package/src/cli/cmd/tui/context/theme/catppuccin.json +0 -112
  150. package/src/cli/cmd/tui/context/theme/cobalt2.json +0 -228
  151. package/src/cli/cmd/tui/context/theme/cursor.json +0 -249
  152. package/src/cli/cmd/tui/context/theme/dracula.json +0 -219
  153. package/src/cli/cmd/tui/context/theme/epochcli.json +0 -245
  154. package/src/cli/cmd/tui/context/theme/everforest.json +0 -241
  155. package/src/cli/cmd/tui/context/theme/flexoki.json +0 -237
  156. package/src/cli/cmd/tui/context/theme/github.json +0 -233
  157. package/src/cli/cmd/tui/context/theme/gruvbox.json +0 -242
  158. package/src/cli/cmd/tui/context/theme/kanagawa.json +0 -77
  159. package/src/cli/cmd/tui/context/theme/lucent-orng.json +0 -237
  160. package/src/cli/cmd/tui/context/theme/material.json +0 -235
  161. package/src/cli/cmd/tui/context/theme/matrix.json +0 -77
  162. package/src/cli/cmd/tui/context/theme/mercury.json +0 -252
  163. package/src/cli/cmd/tui/context/theme/monokai.json +0 -221
  164. package/src/cli/cmd/tui/context/theme/nightowl.json +0 -221
  165. package/src/cli/cmd/tui/context/theme/nord.json +0 -223
  166. package/src/cli/cmd/tui/context/theme/one-dark.json +0 -84
  167. package/src/cli/cmd/tui/context/theme/orng.json +0 -249
  168. package/src/cli/cmd/tui/context/theme/osaka-jade.json +0 -93
  169. package/src/cli/cmd/tui/context/theme/palenight.json +0 -222
  170. package/src/cli/cmd/tui/context/theme/rosepine.json +0 -234
  171. package/src/cli/cmd/tui/context/theme/solarized.json +0 -223
  172. package/src/cli/cmd/tui/context/theme/synthwave84.json +0 -226
  173. package/src/cli/cmd/tui/context/theme/tokyonight.json +0 -243
  174. package/src/cli/cmd/tui/context/theme/vercel.json +0 -245
  175. package/src/cli/cmd/tui/context/theme/vesper.json +0 -218
  176. package/src/cli/cmd/tui/context/theme/zenburn.json +0 -223
  177. package/src/cli/cmd/tui/context/theme.tsx +0 -1236
  178. package/src/cli/cmd/tui/context/tui-config.tsx +0 -9
  179. package/src/cli/cmd/tui/event.ts +0 -48
  180. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +0 -93
  181. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +0 -145
  182. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +0 -50
  183. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +0 -63
  184. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +0 -62
  185. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +0 -93
  186. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +0 -66
  187. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +0 -96
  188. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +0 -48
  189. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +0 -270
  190. package/src/cli/cmd/tui/plugin/api.tsx +0 -397
  191. package/src/cli/cmd/tui/plugin/index.ts +0 -3
  192. package/src/cli/cmd/tui/plugin/internal.ts +0 -27
  193. package/src/cli/cmd/tui/plugin/runtime.ts +0 -1031
  194. package/src/cli/cmd/tui/plugin/slots.tsx +0 -60
  195. package/src/cli/cmd/tui/routes/home.tsx +0 -84
  196. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +0 -65
  197. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +0 -110
  198. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +0 -26
  199. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +0 -47
  200. package/src/cli/cmd/tui/routes/session/footer.tsx +0 -91
  201. package/src/cli/cmd/tui/routes/session/index.tsx +0 -2161
  202. package/src/cli/cmd/tui/routes/session/permission.tsx +0 -691
  203. package/src/cli/cmd/tui/routes/session/question.tsx +0 -468
  204. package/src/cli/cmd/tui/routes/session/sidebar.tsx +0 -70
  205. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +0 -131
  206. package/src/cli/cmd/tui/thread.ts +0 -241
  207. package/src/cli/cmd/tui/ui/dialog-alert.tsx +0 -59
  208. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +0 -89
  209. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +0 -211
  210. package/src/cli/cmd/tui/ui/dialog-help.tsx +0 -40
  211. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +0 -115
  212. package/src/cli/cmd/tui/ui/dialog-select.tsx +0 -417
  213. package/src/cli/cmd/tui/ui/dialog.tsx +0 -192
  214. package/src/cli/cmd/tui/ui/link.tsx +0 -28
  215. package/src/cli/cmd/tui/ui/spinner.ts +0 -368
  216. package/src/cli/cmd/tui/ui/toast.tsx +0 -100
  217. package/src/cli/cmd/tui/util/clipboard.ts +0 -192
  218. package/src/cli/cmd/tui/util/editor.ts +0 -37
  219. package/src/cli/cmd/tui/util/model.ts +0 -23
  220. package/src/cli/cmd/tui/util/provider-origin.ts +0 -20
  221. package/src/cli/cmd/tui/util/scroll.ts +0 -23
  222. package/src/cli/cmd/tui/util/selection.ts +0 -25
  223. package/src/cli/cmd/tui/util/signal.ts +0 -7
  224. package/src/cli/cmd/tui/util/terminal.ts +0 -114
  225. package/src/cli/cmd/tui/util/transcript.ts +0 -112
  226. package/src/cli/cmd/tui/win32.ts +0 -129
  227. package/src/cli/cmd/tui/worker.ts +0 -195
  228. package/src/cli/cmd/uninstall.ts +0 -353
  229. package/src/cli/cmd/upgrade.ts +0 -73
  230. package/src/cli/cmd/web.ts +0 -81
  231. package/src/cli/effect/prompt.ts +0 -25
  232. package/src/cli/error.ts +0 -46
  233. package/src/cli/heap.ts +0 -59
  234. package/src/cli/logo.ts +0 -6
  235. package/src/cli/network.ts +0 -60
  236. package/src/cli/ui.ts +0 -133
  237. package/src/cli/upgrade.ts +0 -31
  238. package/src/command/index.ts +0 -197
  239. package/src/command/template/initialize.txt +0 -66
  240. package/src/command/template/review.txt +0 -101
  241. package/src/config/config.ts +0 -1610
  242. package/src/config/console-state.ts +0 -15
  243. package/src/config/markdown.ts +0 -99
  244. package/src/config/paths.ts +0 -167
  245. package/src/config/tui-migrate.ts +0 -155
  246. package/src/config/tui-schema.ts +0 -37
  247. package/src/config/tui.ts +0 -179
  248. package/src/config/validator.ts +0 -52
  249. package/src/control-plane/adaptors/index.ts +0 -20
  250. package/src/control-plane/adaptors/worktree.ts +0 -42
  251. package/src/control-plane/schema.ts +0 -17
  252. package/src/control-plane/sse.ts +0 -66
  253. package/src/control-plane/types.ts +0 -32
  254. package/src/control-plane/workspace.sql.ts +0 -17
  255. package/src/control-plane/workspace.ts +0 -168
  256. package/src/effect/cross-spawn-spawner.ts +0 -502
  257. package/src/effect/instance-ref.ts +0 -6
  258. package/src/effect/instance-registry.ts +0 -12
  259. package/src/effect/instance-state.ts +0 -82
  260. package/src/effect/run-service.ts +0 -33
  261. package/src/effect/runner.ts +0 -216
  262. package/src/env/index.ts +0 -28
  263. package/src/file/ignore.ts +0 -82
  264. package/src/file/index.ts +0 -686
  265. package/src/file/protected.ts +0 -59
  266. package/src/file/ripgrep.ts +0 -376
  267. package/src/file/time.ts +0 -133
  268. package/src/file/watcher.ts +0 -172
  269. package/src/filesystem/index.ts +0 -236
  270. package/src/flag/flag.ts +0 -157
  271. package/src/format/formatter.ts +0 -413
  272. package/src/format/index.ts +0 -203
  273. package/src/git/index.ts +0 -303
  274. package/src/global/index.ts +0 -54
  275. package/src/id/id.ts +0 -85
  276. package/src/ide/index.ts +0 -74
  277. package/src/index.ts +0 -253
  278. package/src/installation/index.ts +0 -355
  279. package/src/installation/meta.ts +0 -7
  280. package/src/lsp/client.ts +0 -256
  281. package/src/lsp/index.ts +0 -558
  282. package/src/lsp/language.ts +0 -120
  283. package/src/lsp/launch.ts +0 -21
  284. package/src/lsp/server.ts +0 -1968
  285. package/src/mcp/auth.ts +0 -173
  286. package/src/mcp/index.ts +0 -1250
  287. package/src/mcp/oauth-callback.ts +0 -216
  288. package/src/mcp/oauth-provider.ts +0 -185
  289. package/src/mcp/schema-loader.ts +0 -82
  290. package/src/node.ts +0 -1
  291. package/src/npm/index.ts +0 -188
  292. package/src/patch/index.ts +0 -680
  293. package/src/permission/arity.ts +0 -163
  294. package/src/permission/evaluate.ts +0 -15
  295. package/src/permission/index.ts +0 -323
  296. package/src/permission/schema.ts +0 -17
  297. package/src/plugin/cloudflare.ts +0 -67
  298. package/src/plugin/codex.ts +0 -608
  299. package/src/plugin/github-copilot/copilot.ts +0 -361
  300. package/src/plugin/github-copilot/models.ts +0 -144
  301. package/src/plugin/index.ts +0 -288
  302. package/src/plugin/install.ts +0 -439
  303. package/src/plugin/loader.ts +0 -174
  304. package/src/plugin/meta.ts +0 -188
  305. package/src/plugin/shared.ts +0 -323
  306. package/src/project/bootstrap.ts +0 -29
  307. package/src/project/instance.ts +0 -175
  308. package/src/project/project.sql.ts +0 -16
  309. package/src/project/project.ts +0 -519
  310. package/src/project/schema.ts +0 -16
  311. package/src/project/state.ts +0 -70
  312. package/src/project/vcs.ts +0 -240
  313. package/src/provider/auth.ts +0 -253
  314. package/src/provider/error.ts +0 -297
  315. package/src/provider/models.ts +0 -162
  316. package/src/provider/provider.ts +0 -1776
  317. package/src/provider/schema.ts +0 -38
  318. package/src/provider/sdk/copilot/README.md +0 -5
  319. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +0 -170
  320. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +0 -15
  321. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +0 -19
  322. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +0 -64
  323. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +0 -814
  324. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +0 -28
  325. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +0 -44
  326. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +0 -83
  327. package/src/provider/sdk/copilot/copilot-provider.ts +0 -100
  328. package/src/provider/sdk/copilot/index.ts +0 -2
  329. package/src/provider/sdk/copilot/openai-compatible-error.ts +0 -27
  330. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +0 -335
  331. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +0 -22
  332. package/src/provider/sdk/copilot/responses/openai-config.ts +0 -18
  333. package/src/provider/sdk/copilot/responses/openai-error.ts +0 -22
  334. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +0 -214
  335. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +0 -1769
  336. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +0 -173
  337. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +0 -1
  338. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +0 -87
  339. package/src/provider/sdk/copilot/responses/tool/file-search.ts +0 -127
  340. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +0 -114
  341. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +0 -64
  342. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +0 -103
  343. package/src/provider/sdk/copilot/responses/tool/web-search.ts +0 -102
  344. package/src/provider/transform.ts +0 -1124
  345. package/src/pty/index.ts +0 -397
  346. package/src/pty/pty.bun.ts +0 -26
  347. package/src/pty/pty.node.ts +0 -27
  348. package/src/pty/pty.ts +0 -25
  349. package/src/pty/schema.ts +0 -17
  350. package/src/question/index.ts +0 -224
  351. package/src/question/schema.ts +0 -17
  352. package/src/server/error.ts +0 -36
  353. package/src/server/event.ts +0 -7
  354. package/src/server/instance.ts +0 -315
  355. package/src/server/mdns.ts +0 -60
  356. package/src/server/middleware.ts +0 -33
  357. package/src/server/projectors.ts +0 -28
  358. package/src/server/proxy.ts +0 -130
  359. package/src/server/router.ts +0 -105
  360. package/src/server/routes/config.ts +0 -92
  361. package/src/server/routes/event.ts +0 -83
  362. package/src/server/routes/experimental.ts +0 -374
  363. package/src/server/routes/file.ts +0 -197
  364. package/src/server/routes/global.ts +0 -312
  365. package/src/server/routes/mcp.ts +0 -225
  366. package/src/server/routes/permission.ts +0 -69
  367. package/src/server/routes/project.ts +0 -118
  368. package/src/server/routes/provider.ts +0 -171
  369. package/src/server/routes/pty.ts +0 -210
  370. package/src/server/routes/question.ts +0 -99
  371. package/src/server/routes/session.ts +0 -984
  372. package/src/server/routes/tui.ts +0 -378
  373. package/src/server/routes/workspace.ts +0 -94
  374. package/src/server/server.ts +0 -353
  375. package/src/session/compaction.ts +0 -86
  376. package/src/session/index.ts +0 -904
  377. package/src/session/instruction.ts +0 -261
  378. package/src/session/llm/monitor.ts +0 -87
  379. package/src/session/llm.ts +0 -1676
  380. package/src/session/message-v2.ts +0 -1082
  381. package/src/session/message.ts +0 -191
  382. package/src/session/overflow.ts +0 -35
  383. package/src/session/processor.ts +0 -635
  384. package/src/session/projectors.ts +0 -136
  385. package/src/session/prompt/build-switch.txt +0 -5
  386. package/src/session/prompt/builder.ts +0 -135
  387. package/src/session/prompt/default.txt +0 -11
  388. package/src/session/prompt/engine.ts +0 -1072
  389. package/src/session/prompt/gemma4.txt +0 -1
  390. package/src/session/prompt/max-steps.txt +0 -16
  391. package/src/session/prompt/orchestrator.ts +0 -426
  392. package/src/session/prompt/plan.txt +0 -28
  393. package/src/session/prompt/qwen.txt +0 -19
  394. package/src/session/prompt/resolver.ts +0 -670
  395. package/src/session/prompt/router.ts +0 -197
  396. package/src/session/prompt/state.ts +0 -96
  397. package/src/session/prompt/types.ts +0 -115
  398. package/src/session/prompt/utils.ts +0 -15
  399. package/src/session/prompt.ts +0 -362
  400. package/src/session/retry.ts +0 -106
  401. package/src/session/revert.ts +0 -176
  402. package/src/session/sanitizer.ts +0 -125
  403. package/src/session/schema.ts +0 -38
  404. package/src/session/session.sql.ts +0 -106
  405. package/src/session/status.ts +0 -102
  406. package/src/session/summary.ts +0 -183
  407. package/src/session/system.ts +0 -79
  408. package/src/session/todo.ts +0 -166
  409. package/src/session/worker.ts +0 -382
  410. package/src/shell/shell.ts +0 -110
  411. package/src/skill/discovery.ts +0 -116
  412. package/src/skill/index.ts +0 -287
  413. package/src/snapshot/index.ts +0 -726
  414. package/src/sql.d.ts +0 -4
  415. package/src/storage/db.bun.ts +0 -8
  416. package/src/storage/db.node.ts +0 -8
  417. package/src/storage/db.ts +0 -174
  418. package/src/storage/json-migration.ts +0 -387
  419. package/src/storage/schema.sql.ts +0 -10
  420. package/src/storage/schema.ts +0 -4
  421. package/src/storage/storage.ts +0 -353
  422. package/src/sync/README.md +0 -179
  423. package/src/sync/event.sql.ts +0 -16
  424. package/src/sync/index.ts +0 -263
  425. package/src/sync/schema.ts +0 -14
  426. package/src/tool/apply_patch.ts +0 -281
  427. package/src/tool/apply_patch.txt +0 -1
  428. package/src/tool/arbitration.txt +0 -5
  429. package/src/tool/bash.ts +0 -494
  430. package/src/tool/bash.txt +0 -2
  431. package/src/tool/batch.ts +0 -183
  432. package/src/tool/batch.txt +0 -1
  433. package/src/tool/codesearch.ts +0 -132
  434. package/src/tool/codesearch.txt +0 -1
  435. package/src/tool/edit.ts +0 -734
  436. package/src/tool/edit.txt +0 -1
  437. package/src/tool/external-directory.ts +0 -46
  438. package/src/tool/glob.ts +0 -73
  439. package/src/tool/glob.txt +0 -2
  440. package/src/tool/grep.ts +0 -156
  441. package/src/tool/grep.txt +0 -2
  442. package/src/tool/invalid.ts +0 -20
  443. package/src/tool/ls.ts +0 -121
  444. package/src/tool/ls.txt +0 -1
  445. package/src/tool/lsp.ts +0 -97
  446. package/src/tool/lsp.txt +0 -1
  447. package/src/tool/multiedit.ts +0 -46
  448. package/src/tool/multiedit.txt +0 -1
  449. package/src/tool/plan-enter.txt +0 -14
  450. package/src/tool/plan-exit.txt +0 -13
  451. package/src/tool/plan.ts +0 -131
  452. package/src/tool/question.ts +0 -46
  453. package/src/tool/question.txt +0 -10
  454. package/src/tool/read.ts +0 -332
  455. package/src/tool/read.txt +0 -1
  456. package/src/tool/registry.ts +0 -288
  457. package/src/tool/revert.ts +0 -37
  458. package/src/tool/schema.ts +0 -17
  459. package/src/tool/skill.ts +0 -105
  460. package/src/tool/task.ts +0 -150
  461. package/src/tool/task.txt +0 -3
  462. package/src/tool/task_complete.ts +0 -21
  463. package/src/tool/tool.ts +0 -112
  464. package/src/tool/truncate.ts +0 -144
  465. package/src/tool/truncation-dir.ts +0 -4
  466. package/src/tool/webfetch.ts +0 -206
  467. package/src/tool/webfetch.txt +0 -1
  468. package/src/tool/websearch.ts +0 -150
  469. package/src/tool/websearch.txt +0 -1
  470. package/src/tool/write.ts +0 -101
  471. package/src/tool/write.txt +0 -1
  472. package/src/util/abort.ts +0 -35
  473. package/src/util/ai-sdk.ts +0 -59
  474. package/src/util/archive.ts +0 -17
  475. package/src/util/color.ts +0 -19
  476. package/src/util/context.ts +0 -25
  477. package/src/util/data-url.ts +0 -9
  478. package/src/util/defer.ts +0 -12
  479. package/src/util/effect-http-client.ts +0 -11
  480. package/src/util/effect-zod.ts +0 -98
  481. package/src/util/error.ts +0 -77
  482. package/src/util/filesystem.ts +0 -245
  483. package/src/util/flock.ts +0 -333
  484. package/src/util/fn.ts +0 -21
  485. package/src/util/format.ts +0 -20
  486. package/src/util/glob.ts +0 -34
  487. package/src/util/hash.ts +0 -7
  488. package/src/util/iife.ts +0 -3
  489. package/src/util/keybind.ts +0 -103
  490. package/src/util/lazy.ts +0 -23
  491. package/src/util/locale.ts +0 -81
  492. package/src/util/lock.ts +0 -98
  493. package/src/util/log-parser.ts +0 -114
  494. package/src/util/log.ts +0 -250
  495. package/src/util/network.ts +0 -23
  496. package/src/util/process.ts +0 -176
  497. package/src/util/queue.ts +0 -32
  498. package/src/util/record.ts +0 -3
  499. package/src/util/rpc.ts +0 -66
  500. package/src/util/schema.ts +0 -53
  501. package/src/util/scrap.ts +0 -10
  502. package/src/util/session-analyzer.ts +0 -331
  503. package/src/util/session-telemetry.ts +0 -91
  504. package/src/util/signal.ts +0 -12
  505. package/src/util/timeout.ts +0 -14
  506. package/src/util/token.ts +0 -7
  507. package/src/util/tokenizer.ts +0 -50
  508. package/src/util/toon.ts +0 -45
  509. package/src/util/update-schema.ts +0 -13
  510. package/src/util/which.ts +0 -14
  511. package/src/util/wildcard.ts +0 -59
  512. package/src/worktree/index.ts +0 -612
  513. package/sst-env.d.ts +0 -10
  514. package/test/AGENTS.md +0 -81
  515. package/test/account/repo.test.ts +0 -326
  516. package/test/account/service.test.ts +0 -393
  517. package/test/acp/agent-interface.test.ts +0 -51
  518. package/test/acp/event-subscription.test.ts +0 -685
  519. package/test/agent/agent.test.ts +0 -716
  520. package/test/auth/auth.test.ts +0 -58
  521. package/test/bus/bus-effect.test.ts +0 -164
  522. package/test/bus/bus-integration.test.ts +0 -87
  523. package/test/bus/bus.test.ts +0 -219
  524. package/test/cli/account.test.ts +0 -26
  525. package/test/cli/cmd/tui/prompt-part.test.ts +0 -47
  526. package/test/cli/github-action.test.ts +0 -198
  527. package/test/cli/github-remote.test.ts +0 -80
  528. package/test/cli/plugin-auth-picker.test.ts +0 -120
  529. package/test/cli/tui/keybind-plugin.test.ts +0 -90
  530. package/test/cli/tui/plugin-add.test.ts +0 -107
  531. package/test/cli/tui/plugin-install.test.ts +0 -89
  532. package/test/cli/tui/plugin-lifecycle.test.ts +0 -225
  533. package/test/cli/tui/plugin-loader-entrypoint.test.ts +0 -492
  534. package/test/cli/tui/plugin-loader-pure.test.ts +0 -72
  535. package/test/cli/tui/plugin-loader.test.ts +0 -752
  536. package/test/cli/tui/plugin-toggle.test.ts +0 -159
  537. package/test/cli/tui/slot-replace.test.tsx +0 -47
  538. package/test/cli/tui/theme-store.test.ts +0 -51
  539. package/test/cli/tui/thread.test.ts +0 -128
  540. package/test/cli/tui/transcript.test.ts +0 -426
  541. package/test/config/agent-color.test.ts +0 -71
  542. package/test/config/config.test.ts +0 -2337
  543. package/test/config/fixtures/empty-frontmatter.md +0 -4
  544. package/test/config/fixtures/frontmatter.md +0 -28
  545. package/test/config/fixtures/markdown-header.md +0 -11
  546. package/test/config/fixtures/no-frontmatter.md +0 -1
  547. package/test/config/fixtures/weird-model-id.md +0 -13
  548. package/test/config/markdown.test.ts +0 -228
  549. package/test/config/tui.test.ts +0 -800
  550. package/test/control-plane/sse.test.ts +0 -56
  551. package/test/effect/cross-spawn-spawner.test.ts +0 -412
  552. package/test/effect/instance-state.test.ts +0 -482
  553. package/test/effect/run-service.test.ts +0 -46
  554. package/test/effect/runner.test.ts +0 -523
  555. package/test/fake/provider.ts +0 -82
  556. package/test/file/fsmonitor.test.ts +0 -62
  557. package/test/file/ignore.test.ts +0 -10
  558. package/test/file/index.test.ts +0 -946
  559. package/test/file/path-traversal.test.ts +0 -198
  560. package/test/file/ripgrep.test.ts +0 -54
  561. package/test/file/time.test.ts +0 -445
  562. package/test/file/watcher.test.ts +0 -247
  563. package/test/filesystem/filesystem.test.ts +0 -319
  564. package/test/fixture/db.ts +0 -11
  565. package/test/fixture/fixture.test.ts +0 -26
  566. package/test/fixture/fixture.ts +0 -172
  567. package/test/fixture/flock-worker.ts +0 -72
  568. package/test/fixture/lsp/fake-lsp-server.js +0 -77
  569. package/test/fixture/plug-worker.ts +0 -93
  570. package/test/fixture/plugin-meta-worker.ts +0 -26
  571. package/test/fixture/skills/agents-sdk/SKILL.md +0 -152
  572. package/test/fixture/skills/agents-sdk/references/callable.md +0 -92
  573. package/test/fixture/skills/cloudflare/SKILL.md +0 -211
  574. package/test/fixture/skills/index.json +0 -6
  575. package/test/fixture/tui-plugin.ts +0 -328
  576. package/test/fixture/tui-runtime.ts +0 -27
  577. package/test/format/format.test.ts +0 -171
  578. package/test/git/git.test.ts +0 -128
  579. package/test/ide/ide.test.ts +0 -82
  580. package/test/installation/installation.test.ts +0 -152
  581. package/test/keybind.test.ts +0 -421
  582. package/test/lib/effect.ts +0 -53
  583. package/test/lib/filesystem.ts +0 -10
  584. package/test/lib/llm-server.ts +0 -794
  585. package/test/lsp/client.test.ts +0 -95
  586. package/test/lsp/index.test.ts +0 -133
  587. package/test/lsp/launch.test.ts +0 -22
  588. package/test/lsp/lifecycle.test.ts +0 -147
  589. package/test/mcp/headers.test.ts +0 -153
  590. package/test/mcp/lifecycle.test.ts +0 -750
  591. package/test/mcp/oauth-auto-connect.test.ts +0 -199
  592. package/test/mcp/oauth-browser.test.ts +0 -249
  593. package/test/mcp/sc-approve-validator.test.ts +0 -431
  594. package/test/memory/abort-leak.test.ts +0 -137
  595. package/test/npm.test.ts +0 -18
  596. package/test/patch/patch.test.ts +0 -348
  597. package/test/permission/arity.test.ts +0 -33
  598. package/test/permission/next.test.ts +0 -1123
  599. package/test/permission-task.test.ts +0 -323
  600. package/test/plugin/auth-override.test.ts +0 -74
  601. package/test/plugin/codex.test.ts +0 -123
  602. package/test/plugin/github-copilot-models.test.ts +0 -117
  603. package/test/plugin/install-concurrency.test.ts +0 -140
  604. package/test/plugin/install.test.ts +0 -570
  605. package/test/plugin/loader-shared.test.ts +0 -1136
  606. package/test/plugin/meta.test.ts +0 -137
  607. package/test/plugin/shared.test.ts +0 -88
  608. package/test/plugin/trigger.test.ts +0 -111
  609. package/test/preload.ts +0 -90
  610. package/test/project/migrate-global.test.ts +0 -140
  611. package/test/project/project.test.ts +0 -459
  612. package/test/project/state.test.ts +0 -115
  613. package/test/project/vcs.test.ts +0 -228
  614. package/test/project/worktree-remove.test.ts +0 -96
  615. package/test/project/worktree.test.ts +0 -173
  616. package/test/provider/amazon-bedrock.test.ts +0 -447
  617. package/test/provider/copilot/convert-to-copilot-messages.test.ts +0 -523
  618. package/test/provider/copilot/copilot-chat-model.test.ts +0 -592
  619. package/test/provider/error.test.ts +0 -49
  620. package/test/provider/gitlab-duo.test.ts +0 -412
  621. package/test/provider/provider.test.ts +0 -2494
  622. package/test/provider/transform.test.ts +0 -2944
  623. package/test/pty/pty-output-isolation.test.ts +0 -141
  624. package/test/pty/pty-session.test.ts +0 -92
  625. package/test/pty/pty-shell.test.ts +0 -59
  626. package/test/question/question.test.ts +0 -453
  627. package/test/server/global-session-list.test.ts +0 -89
  628. package/test/server/project-init-git.test.ts +0 -121
  629. package/test/server/session-actions.test.ts +0 -83
  630. package/test/server/session-list.test.ts +0 -98
  631. package/test/server/session-messages.test.ts +0 -159
  632. package/test/server/session-select.test.ts +0 -84
  633. package/test/session/compaction.test.ts +0 -683
  634. package/test/session/continuity-handover.test.ts +0 -620
  635. package/test/session/deterministic-handover.test.ts +0 -328
  636. package/test/session/doom-protection.test.ts +0 -247
  637. package/test/session/hard-reset.test.ts +0 -179
  638. package/test/session/instruction.test.ts +0 -286
  639. package/test/session/llm/monitor.test.ts +0 -53
  640. package/test/session/llm-sanitizer.test.ts +0 -90
  641. package/test/session/llm-zones-e2e.test.ts +0 -61
  642. package/test/session/llm.test.ts +0 -1308
  643. package/test/session/mcpx-normalization.test.ts +0 -86
  644. package/test/session/mcpx-syntax-recovery.test.ts +0 -28
  645. package/test/session/message-v2.test.ts +0 -957
  646. package/test/session/messages-pagination.test.ts +0 -885
  647. package/test/session/processor-effect.test.ts +0 -805
  648. package/test/session/prompt/builder.test.ts +0 -71
  649. package/test/session/prompt/engine-loop.test.ts +0 -80
  650. package/test/session/prompt/orchestrator.test.ts +0 -108
  651. package/test/session/prompt/resolver.test.ts +0 -211
  652. package/test/session/prompt/router.test.ts +0 -84
  653. package/test/session/prompt/state.test.ts +0 -57
  654. package/test/session/prompt-effect.test.ts +0 -1241
  655. package/test/session/prompt.test.ts +0 -522
  656. package/test/session/refactor-system-zones.test.ts +0 -241
  657. package/test/session/retry.test.ts +0 -232
  658. package/test/session/revert-compact.test.ts +0 -621
  659. package/test/session/sanitizer.test.ts +0 -61
  660. package/test/session/session.test.ts +0 -142
  661. package/test/session/snapshot-tool-race.test.ts +0 -242
  662. package/test/session/structured-output-integration.test.ts +0 -233
  663. package/test/session/structured-output.test.ts +0 -391
  664. package/test/session/system.test.ts +0 -59
  665. package/test/session/telemetry.test.ts +0 -35
  666. package/test/shell/shell.test.ts +0 -73
  667. package/test/skill/discovery.test.ts +0 -116
  668. package/test/skill/skill.test.ts +0 -392
  669. package/test/snapshot/snapshot.test.ts +0 -1404
  670. package/test/storage/db.test.ts +0 -14
  671. package/test/storage/json-migration.test.ts +0 -791
  672. package/test/storage/storage.test.ts +0 -295
  673. package/test/sync/index.test.ts +0 -191
  674. package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
  675. package/test/tool/apply_patch.test.ts +0 -567
  676. package/test/tool/bash.test.ts +0 -1099
  677. package/test/tool/edit.test.ts +0 -681
  678. package/test/tool/external-directory.test.ts +0 -198
  679. package/test/tool/fixtures/large-image.png +0 -0
  680. package/test/tool/fixtures/models-api.json +0 -65179
  681. package/test/tool/grep.test.ts +0 -111
  682. package/test/tool/question.test.ts +0 -126
  683. package/test/tool/read.test.ts +0 -468
  684. package/test/tool/registry.test.ts +0 -126
  685. package/test/tool/skill.test.ts +0 -167
  686. package/test/tool/task.test.ts +0 -49
  687. package/test/tool/tool-define.test.ts +0 -101
  688. package/test/tool/truncation.test.ts +0 -161
  689. package/test/tool/webfetch.test.ts +0 -101
  690. package/test/tool/write.test.ts +0 -354
  691. package/test/util/data-url.test.ts +0 -14
  692. package/test/util/effect-zod.test.ts +0 -61
  693. package/test/util/error.test.ts +0 -38
  694. package/test/util/filesystem.test.ts +0 -656
  695. package/test/util/flock.test.ts +0 -383
  696. package/test/util/format.test.ts +0 -59
  697. package/test/util/glob.test.ts +0 -164
  698. package/test/util/iife.test.ts +0 -36
  699. package/test/util/lazy.test.ts +0 -50
  700. package/test/util/lock.test.ts +0 -72
  701. package/test/util/log-parser.test.ts +0 -61
  702. package/test/util/module.test.ts +0 -59
  703. package/test/util/process.test.ts +0 -128
  704. package/test/util/telemetry-integration.test.ts +0 -104
  705. package/test/util/timeout.test.ts +0 -21
  706. package/test/util/which.test.ts +0 -100
  707. package/test/util/wildcard.test.ts +0 -90
  708. package/test-regex.js +0 -50
  709. package/tsconfig.json +0 -23
  710. /package/bin/{epochcli → epochcli.cjs} +0 -0
  711. /package/{script/postinstall.mjs → postinstall.mjs} +0 -0
@@ -1,1676 +0,0 @@
1
- import { Provider } from "@/provider/provider"
2
- import { Log } from "@/util/log"
3
- import { SessionTelemetry } from "@/util/session-telemetry"
4
- import { Cause, Effect, Layer, Record, ServiceMap } from "effect"
5
- import * as Queue from "effect/Queue"
6
- import * as Stream from "effect/Stream"
7
- import { streamText, wrapLanguageModel, type ModelMessage, type Tool, tool, jsonSchema, generateText } from "@/util/ai-sdk"
8
- import Ajv from "ajv"
9
-
10
- const ajv = new Ajv({
11
- strict: false,
12
- allErrors: true,
13
- })
14
-
15
- import { mergeDeep, pipe } from "remeda"
16
- import { GitLabWorkflowLanguageModel } from "gitlab-ai-provider"
17
- import { ProviderTransform } from "@/provider/transform"
18
- import { Config } from "@/config/config"
19
- import { Instance } from "@/project/instance"
20
- import type { Agent } from "@/agent/agent"
21
- import { MessageV2 } from "./message-v2"
22
- import { SanitizerMiddleware } from "./sanitizer"
23
- import { Plugin } from "@/plugin"
24
- import { SystemPrompt } from "./system"
25
- import { PromptBuilder, type ZoneStructuredPayload } from "./prompt/builder"
26
- import { Flag } from "@/flag/flag"
27
- import { Permission } from "@/permission"
28
- import { Auth } from "@/auth"
29
- import { Installation } from "@/installation"
30
- import { ToonEncoder } from "@/util/toon"
31
- import { MCP } from "@/mcp/index"
32
- import { StreamingMonitor } from "./llm/monitor"
33
- import { SchemaContextLoader } from "@/mcp/schema-loader"
34
-
35
- export namespace LLM {
36
- const log = Log.create({ service: "llm" })
37
- export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX
38
-
39
- interface SessionMetadata {
40
- phaseTurnCount: number
41
- lastPhase: string
42
- consecutiveFailures: Map<string, number>
43
- }
44
-
45
- const sessionMetadata = new Map<string, SessionMetadata>()
46
-
47
- export type StreamInput = {
48
- user: MessageV2.User
49
- sessionID: string
50
- parentSessionID?: string
51
- model: Provider.Model
52
- agent: Agent.Info
53
- permission?: Permission.Ruleset
54
- system: { zone1: string[]; zone2: string[] }
55
- operationalFacts?: string[]
56
- instructions?: string[]
57
- messages: ModelMessage[]
58
- small?: boolean
59
- yolo?: boolean
60
- isContinue?: boolean
61
- tools: Record<string, Tool>
62
- retries?: number
63
- toolChoice?: "auto" | "required" | "none"
64
- }
65
-
66
- export type StreamRequest = StreamInput & {
67
- abort: AbortSignal
68
- }
69
-
70
- export type Event = Awaited<ReturnType<typeof stream>>["fullStream"] extends AsyncIterable<infer T> ? T : never
71
-
72
- export interface Interface {
73
- readonly stream: (input: StreamInput) => Stream.Stream<Event, unknown>
74
- }
75
-
76
- export class Service extends ServiceMap.Service<Service, Interface>()("@epochcli/LLM") {}
77
-
78
- export const layer = Layer.effect(
79
- Service,
80
- Effect.gen(function* () {
81
- return Service.of({
82
- stream(input) {
83
- return Stream.scoped(
84
- Stream.unwrap(
85
- Effect.gen(function* () {
86
- const ctrl = yield* Effect.acquireRelease(
87
- Effect.sync(() => new AbortController()),
88
- (ctrl) => Effect.sync(() => ctrl.abort()),
89
- )
90
-
91
- const result = yield* Effect.promise(() => LLM.stream({ ...input, abort: ctrl.signal }))
92
-
93
- // Robustly suppress unhandled rejections on known background promises returned by the AI SDK
94
- // Explicitly access getters because for...in misses non-enumerable properties on the prototype.
95
- const promiseKeys = ["text", "usage", "finishReason", "toolCalls", "toolResults", "warnings", "providerMetadata", "steps", "response"]
96
- for (const key of promiseKeys) {
97
- try {
98
- const value = (result as any)[key]
99
- if (value instanceof Promise) {
100
- value.catch(() => {})
101
- }
102
- } catch {}
103
- }
104
-
105
- return Stream.fromAsyncIterable(result.fullStream, (e) =>
106
- e instanceof Error ? e : new Error(String(e)),
107
- )
108
- }),
109
- ),
110
- )
111
- },
112
- })
113
- }),
114
- )
115
-
116
- export const defaultLayer = layer
117
-
118
- export function parseGroundTruthRules(
119
- raw: string,
120
- activePacks: string[] = ["core_interaction_pack"],
121
- contextLimit?: number,
122
- ): { operationalFacts: string; behavioralRules: string; projectSpecific: string } {
123
- const zones = {
124
- operationalFacts: "",
125
- behavioralRules: "",
126
- projectSpecific: "",
127
- }
128
-
129
- const zone1Match = raw.match(/(ZONE 1 & 3:.*?)(?=ZONE 2:|$)/s)
130
- if (zone1Match) {
131
- const factsStr = zone1Match[1]
132
- const factsRegex = /fact_\d+,\s*"[^"]+",\s*"[^"]+",\s*"([^"]+)"/g
133
- const extractedFacts: string[] = []
134
- let match
135
- while ((match = factsRegex.exec(factsStr)) !== null) {
136
- let fact = match[1]
137
- // Dynamic Context Limit Injection
138
- if (fact.includes("[CONTEXT_LIMIT]")) {
139
- const limitStr = `${Math.round((contextLimit ?? 32000) / 1000)}K tokens`
140
- fact = fact.replace("[CONTEXT_LIMIT]", limitStr)
141
- }
142
- extractedFacts.push(`- ${fact}`)
143
- }
144
-
145
- if (extractedFacts.length > 0) {
146
- zones.operationalFacts = `ZONE 1 & 3: OPERATIONAL FACTS\n${extractedFacts.join("\n")}`
147
- } else {
148
- zones.operationalFacts = factsStr.trim()
149
- }
150
- }
151
-
152
- const zone2Match = raw.match(/(ZONE 2: BEHAVIORAL RULE PACKS.*?)(?=ZONE 3: PROJECT-SPECIFIC RULES|$)/s)
153
- if (zone2Match) {
154
- const zone2Str = zone2Match[1]
155
-
156
- const ruleIds = new Set<string>()
157
- for (const targetPack of activePacks) {
158
- const packRegex = new RegExp(`${targetPack}:\\s*\\[(.*?)\\]`)
159
- const packMatch = zone2Str.match(packRegex)
160
- if (packMatch) {
161
- packMatch[1].split(",").forEach((s: string) => {
162
- const id = s.trim().split(".").pop() || ""
163
- if (id) ruleIds.add(id)
164
- })
165
- }
166
- }
167
-
168
- if (ruleIds.size > 0) {
169
- const rulesRegex = /([a-z]+_\d+),\s*"([^"]+)",\s*"([^"]+)",\s*"([^"]+)"/g
170
- const extractedRules: string[] = []
171
- let rMatch
172
- while ((rMatch = rulesRegex.exec(zone2Str)) !== null) {
173
- if (ruleIds.has(rMatch[1])) {
174
- extractedRules.push(`Trigger: ${rMatch[2]}\nBehaviour: ${rMatch[3]}\nExample: ${rMatch[4]}\n`)
175
- }
176
- }
177
-
178
- if (extractedRules.length > 0) {
179
- zones.behavioralRules = `ZONE 2: BEHAVIORAL RULES\n\n${extractedRules.join("\n")}`
180
- } else {
181
- zones.behavioralRules = zone2Str.trim()
182
- }
183
- } else {
184
- zones.behavioralRules = zone2Str.trim()
185
- }
186
- }
187
-
188
- const zone3Specific = raw.match(/(ZONE 3: PROJECT-SPECIFIC RULES.*?)$/s)
189
- if (zone3Specific) zones.projectSpecific = zone3Specific[1].trim()
190
-
191
- if (!zones.operationalFacts && !zones.behavioralRules && !zones.projectSpecific) {
192
- zones.behavioralRules = raw.trim()
193
- }
194
-
195
- return zones
196
- }
197
-
198
- export async function stream(input: StreamRequest) {
199
- const l = log
200
- .clone()
201
- .tag("providerID", input.model.providerID)
202
- .tag("modelID", input.model.id)
203
- .tag("sessionID", input.sessionID)
204
- .tag("small", (input.small ?? false).toString())
205
- .tag("agent", input.agent.name)
206
- .tag("mode", input.agent.mode)
207
- l.info("stream", {
208
- modelID: input.model.id,
209
- providerID: input.model.providerID,
210
- })
211
-
212
- // Update Session Metadata for Turn Tracking & Stagnation Detection
213
- let meta = sessionMetadata.get(input.sessionID)
214
- if (!meta) {
215
- meta = { phaseTurnCount: 0, lastPhase: input.agent.name, consecutiveFailures: new Map() }
216
- sessionMetadata.set(input.sessionID, meta)
217
- }
218
-
219
- if (meta.lastPhase !== input.agent.name) {
220
- meta.phaseTurnCount = 0
221
- meta.lastPhase = input.agent.name
222
- }
223
- meta.phaseTurnCount++
224
-
225
- const [language, cfg, providerInfo, auth] = await Promise.all([
226
- Provider.getLanguage(input.model),
227
- Config.get(),
228
- Provider.getProvider(input.model.providerID),
229
- Auth.get(input.model.providerID),
230
- ])
231
- // TODO: move this to a proper hook
232
- const isOpenaiOauth = providerInfo.id === "openai" && auth?.type === "oauth"
233
-
234
- const payload: ZoneStructuredPayload = {
235
- zone1_critical_rules: [`Current Phase: [${input.agent.name.toUpperCase()}].`],
236
- zone2_context_files: [],
237
- zone3_active_cursor: [],
238
- zone4_guidelines: [],
239
- }
240
-
241
- try {
242
- const fsNode = await import("fs/promises")
243
- const pathNode = await import("path")
244
- const lastUsedPath = pathNode.join(Instance.directory, ".spec_last_used")
245
- const lastUsed = await fsNode.readFile(lastUsedPath, "utf-8").catch(() => null)
246
- if (lastUsed) {
247
- payload.zone1_critical_rules.push(`ACTIVE PROJECT CONTEXT:\n- Feature Path: ${lastUsed.trim()}\n- Note: Subagents MUST NOT run \`sc_init\` if an active feature path is already established. Use \`map\` tools to explore this path.`)
248
- }
249
- } catch (e) {
250
- l.debug("Failed to fetch active project context for subagent", { error: String(e) })
251
- }
252
-
253
- if (input.operationalFacts && input.operationalFacts.length > 0) {
254
- payload.zone1_critical_rules.push(...input.operationalFacts)
255
- }
256
-
257
- if (input.system?.zone1) {
258
- payload.zone1_critical_rules.push(...input.system.zone1)
259
- }
260
-
261
- if (input.yolo) {
262
- payload.zone1_critical_rules.push(
263
- "CRITICAL: YOLO mode is active. You MUST execute tasks autonomously until the work is completely finished. " +
264
- "You are NOT allowed to stop and ask for user input. " +
265
- "When AND ONLY WHEN the entire job is done, you MUST call the 'task_complete' tool to terminate the session.",
266
- )
267
- }
268
-
269
- payload.zone1_critical_rules.push(
270
- "ENVIRONMENT CONTEXT: You are operating in a context-constrained environment. " +
271
- "You MUST work strictly file-by-file. NEVER attempt to create or modify multiple files in a single response or generate massive code blocks at once. " +
272
- "Wait for the tool result confirmation before proceeding to the next file.",
273
- )
274
-
275
- if (input.instructions && input.instructions.length > 0) {
276
- payload.zone4_guidelines.push(...input.instructions)
277
- }
278
-
279
- // Phase Stagnation Detection (Task 3.2)
280
- if (input.agent.name === "plan" && meta.phaseTurnCount >= 8) {
281
- const nudge = `Supervisor Note: You have been in the [PLAN] phase for ${meta.phaseTurnCount} turns. If the implementation plan is complete and tasks are defined, you should run 'sc_approve' to transition to the [BUILD] phase.`
282
- payload.zone1_critical_rules.push(nudge)
283
- l.info("stagnation nudge", { turnCount: meta.phaseTurnCount })
284
- }
285
-
286
- if (input.agent.name === "build" && meta.phaseTurnCount >= 5) {
287
- const nudge = `Supervisor Note: You have been in the [BUILD] phase for ${meta.phaseTurnCount} turns. You should proceed immediately to implementation using the 'write' or 'edit' tools to advance the project state.`
288
- payload.zone1_critical_rules.push(nudge)
289
- l.info("stagnation nudge", { turnCount: meta.phaseTurnCount, phase: "build" })
290
- }
291
-
292
- let activeRulePacks: string[] = ["core_interaction_pack"]
293
- let thinkingEffort: "high" | "low" = "low"
294
-
295
- // Phase 1: Intent Classification (Clerk / local-side) - Task 1.1
296
- // The Clerk detects user intent and shifts the active epochcli Agent.
297
- if (providerInfo.id === "local-main") {
298
- try {
299
- const sideModel = await Provider.getSideModel()
300
- if (sideModel) {
301
- const sideLanguage = await Provider.getLanguage(sideModel)
302
-
303
- // Extract conversation tail for structural context (Task 1.1)
304
- const tailCount = 10
305
- const recentMessages = input.messages.slice(-tailCount)
306
- const conversationTail = recentMessages
307
- .map((m) => {
308
- let content = ""
309
- if (typeof m.content === "string") {
310
- content = m.content
311
- } else if (Array.isArray(m.content)) {
312
- content = m.content
313
- .map((c) => {
314
- if (c.type === "text") return c.text
315
- if (c.type === "tool-call") return `[Tool Call: ${c.toolName}]`
316
- if (c.type === "tool-result") return `[Tool Result: ${c.toolName}]`
317
- return `[${c.type}]`
318
- })
319
- .join(" ")
320
- }
321
- // Truncate individual message content to keep the transcript lean
322
- const truncated = content.length > 300 ? content.slice(0, 250) + "... [truncated]" : content
323
- return `${m.role.toUpperCase()}: ${truncated}`
324
- })
325
- .join("\n\n")
326
-
327
- if (conversationTail) {
328
- const { RuleRouter } = await import("./prompt/router")
329
- const { Agent } = await import("@/agent/agent")
330
-
331
- // Debug info for the Clerk Turn
332
- const sideProvider = await Provider.getProvider(sideModel.providerID)
333
- l.debug("clerk", {
334
- message: `Using model: ${sideModel.providerID}/${sideModel.id} at ${sideProvider?.options?.baseURL}`,
335
- })
336
-
337
- let groundTruths = ""
338
- let continuityReportContext = ""
339
- try {
340
- const fsNode = await import("fs/promises")
341
- const pathNode = await import("path")
342
- const rulesPath = pathNode.join(".history", "project_rules.toon")
343
- const rulesContext = await fsNode.readFile(rulesPath, "utf-8").catch(() => "")
344
- // Use an empty array for packs here since we just want operational facts for the Clerk
345
- if (rulesContext) {
346
- const parsedRules = parseGroundTruthRules(rulesContext, [], sideModel.limit.context)
347
- if (parsedRules.operationalFacts) groundTruths = parsedRules.operationalFacts
348
- }
349
-
350
- // Load continuity report to help Clerk understand overarching state
351
- try {
352
- const continuityContent = await fsNode.readFile(".epoch-continuity.toon", "utf-8")
353
- // Truncate if it's absurdly large, though toon files should be managed.
354
- continuityReportContext =
355
- continuityContent.length > 2000
356
- ? continuityContent.slice(0, 2000) + "... [truncated]"
357
- : continuityContent
358
- } catch (e) {
359
- // File might not exist yet (first epoch), ignore
360
- }
361
- } catch (e) {}
362
-
363
- l.debug("clerk", { message: "Supervising conversation..." })
364
-
365
- // Task 4.2: Arbitration Mechanism
366
- let identifiedAgent: string | undefined
367
-
368
- // Check for initial persona lock (Spec CLI One-Shot)
369
- const firstUserMsg = input.messages.find((m) => m.role === "user")
370
- let firstMsgText = ""
371
- if (typeof firstUserMsg?.content === "string") {
372
- firstMsgText = firstUserMsg.content
373
- } else if (Array.isArray(firstUserMsg?.content)) {
374
- firstMsgText = firstUserMsg.content
375
- .filter((c) => c.type === "text")
376
- .map((c) => c.text)
377
- .join("\n")
378
- }
379
- const isOneShot =
380
- firstMsgText.toLowerCase().includes("one-shot") || firstMsgText.toLowerCase().includes("spec cli")
381
-
382
- // Check if planning is actually finished
383
- let planningFinished = false
384
- try {
385
- const fsNode = await import("fs/promises")
386
- const pathNode = await import("path")
387
- // We don't know the feature name easily here without parsing,
388
- // but we can look for any .spec-tasks-approved file in projects/active
389
- const projectDir = "projects/active"
390
- const entries = await fsNode.readdir(projectDir, { withFileTypes: true })
391
- for (const entry of entries) {
392
- if (entry.isDirectory()) {
393
- const approvedFile = pathNode.join(projectDir, entry.name, ".spec-tasks-approved")
394
- const exists = await fsNode
395
- .access(approvedFile)
396
- .then(() => true)
397
- .catch(() => false)
398
- if (exists) {
399
- planningFinished = true
400
- break
401
- }
402
- }
403
- }
404
- } catch (e) {}
405
-
406
- // Check for recent objections to the supervisor
407
- // Give the proxy a moment of silence between the end of the last turn and the start of supervision
408
- await new Promise((r) => setTimeout(r, 1000))
409
-
410
- // Concurrently identify agent, rule packs, and thinking effort
411
- const [identifiedAgentResult, identifiedPacks, identifiedEffort] = await Promise.all([
412
- (async () => {
413
- if (isOneShot && !planningFinished) {
414
- l.debug("clerk", { message: "One-Shot planning in progress. Locking persona to: plan" })
415
- return "plan"
416
- } else if (planningFinished) {
417
- l.debug("clerk", { message: "Planning finished semaphore detected. Deterministic handover to: build" })
418
- return "build"
419
- } else {
420
- return RuleRouter.identifyAgent(conversationTail, input.agent.name, continuityReportContext)
421
- }
422
- })(),
423
- RuleRouter.identifyRulePacks(conversationTail, sideLanguage),
424
- RuleRouter.identifyThinkingEffort(conversationTail, sideLanguage),
425
- ])
426
-
427
- identifiedAgent = identifiedAgentResult
428
- activeRulePacks = identifiedPacks
429
- thinkingEffort = identifiedEffort
430
-
431
- l.debug("clerk", { message: `Identified agent: ${identifiedAgent}` })
432
- l.debug("clerk", { message: `Identified rule packs: ${activeRulePacks.join(", ")}` })
433
- l.debug("clerk", { message: `Identified thinking effort: ${thinkingEffort}` })
434
-
435
- if (identifiedAgent !== input.agent.name) {
436
- log.debug("Clerk identified agent shift", { from: input.agent.name, to: identifiedAgent })
437
- const newAgent = await Agent.get(identifiedAgent)
438
- if (newAgent) {
439
- l.debug("clerk", { message: `SHIFTING agent to: ${identifiedAgent}` })
440
- input.agent = newAgent
441
- // Update the directive in Zone 1
442
- payload.zone1_critical_rules[0] = `Current Phase: [${input.agent.name.toUpperCase()}].`
443
- }
444
- }
445
- }
446
- }
447
- } catch (e) {
448
- l.error("clerk", { message: "Intent classification failed", error: String(e) })
449
- log.warn("Clerk intent classification failed", { error: String(e) })
450
- }
451
- }
452
-
453
- if (providerInfo.id === "local-main") {
454
- try {
455
- let rulesContext = ""
456
- const mcpClientsRecord = await MCP.clients()
457
- const mcpClients = Object.values(mcpClientsRecord) as any[]
458
- const gtCli = mcpClients.find((c: any) => c.id === "ground")
459
- if (gtCli) {
460
- log.debug("Fetching ground truth rules")
461
- const gtRes = await gtCli.client.callTool({ name: "gt_status", arguments: {} })
462
- if (gtRes.content && gtRes.content.length > 0 && gtRes.content[0].type === "text") {
463
- rulesContext = gtRes.content[0].text
464
- }
465
- } else {
466
- // Fallback rule load
467
- try {
468
- const fsNode = await import("fs/promises")
469
- const pathNode = await import("path")
470
- rulesContext = await fsNode.readFile(pathNode.join(".history", "project_rules.toon"), "utf-8")
471
- } catch (e) {
472
- // ignore missing file
473
- }
474
- }
475
-
476
- if (rulesContext) {
477
- const parsedRules = parseGroundTruthRules(rulesContext, activeRulePacks, input.model.limit.context)
478
- if (parsedRules.operationalFacts) {
479
- payload.zone1_critical_rules.push(parsedRules.operationalFacts)
480
- }
481
- if (parsedRules.behavioralRules) payload.zone2_context_files.push(parsedRules.behavioralRules)
482
- if (parsedRules.projectSpecific) payload.zone3_active_cursor.push(parsedRules.projectSpecific)
483
- }
484
- } catch (e) {
485
- log.warn("Phase 1 Pre-Generation Rules Context fetch failed", { error: String(e) })
486
- }
487
- }
488
-
489
- payload.zone2_context_files.push(
490
- [
491
- // use agent prompt otherwise provider prompt
492
- ...(input.agent.prompt ? [input.agent.prompt] : SystemPrompt.provider(input.model, input.isContinue)),
493
- // any custom prompt passed into this call
494
- ...(input.system?.zone2 ?? []),
495
- // any custom prompt from last user message
496
- ...(input.user.system ? [input.user.system] : []),
497
- ]
498
- .filter((x) => x)
499
- .join("\n\n"),
500
- )
501
-
502
- if (input.user.cursorContext) {
503
- const { file, line, code } = input.user.cursorContext
504
- payload.zone3_active_cursor.push(
505
- `Active Cursor Context:\n File: ${file}\n Line ${line}: ${code} # <--- CURSOR HERE`,
506
- )
507
- }
508
-
509
- const system: string[] = [PromptBuilder.build(payload)]
510
- const header = system[0]
511
- await Plugin.trigger(
512
- "experimental.chat.system.transform",
513
- { sessionID: input.sessionID, model: input.model },
514
- { system },
515
- )
516
- // rejoin to maintain 2-part structure for caching if header unchanged
517
- if (system.length > 2 && system[0] === header) {
518
- const rest = system.slice(1)
519
- system.length = 0
520
- system.push(header, rest.join("\n"))
521
- }
522
-
523
- const isWorkflow = language instanceof GitLabWorkflowLanguageModel
524
- const isSmallReasoningModel =
525
- input.model.api?.id?.includes("gemma-4") ||
526
- input.model.api?.id?.includes("google-gemma-26b") ||
527
- input.model.api?.id?.toLowerCase().includes("qwen") ||
528
- input.model.id?.includes("big-pickle")
529
- const isReasoningModel = input.model.capabilities.reasoning || isSmallReasoningModel
530
-
531
- const variant =
532
- !input.small && input.model.variants && input.user.model.variant
533
- ? input.model.variants[input.user.model.variant]
534
- : {}
535
- const base = input.small
536
- ? ProviderTransform.smallOptions(input.model)
537
- : ProviderTransform.options({
538
- model: input.model,
539
- sessionID: input.sessionID,
540
- providerOptions: providerInfo.options,
541
- thinkingEffort: isReasoningModel ? thinkingEffort : undefined,
542
- })
543
- const options: Record<string, any> = pipe(
544
- base,
545
- mergeDeep(input.model.options),
546
- mergeDeep(input.agent.options),
547
- mergeDeep(variant),
548
- )
549
- if (input.operationalFacts && input.operationalFacts.length > 0) {
550
- options.operationalFacts = input.operationalFacts
551
- }
552
- if (isOpenaiOauth) {
553
- options.instructions = system.join("\n")
554
- }
555
-
556
- // Phase 1: Context Fetching skipped (agent uses mcpx tool directly)
557
- if (false) {
558
- try {
559
- let mcpContext = ""
560
- const mcpxTool = input.tools["mcpx"]
561
-
562
- let activePath = "."
563
-
564
- if (mcpxTool) {
565
- try {
566
- log.debug("Fetching current state from spec via mcpx")
567
- const statusRes = await mcpxTool.execute!({ server: "spec", tool: "sc_status", flags: {} }, options as any)
568
- if (statusRes.content && statusRes.content.length > 0 && statusRes.content[0].type === "text") {
569
- const text = statusRes.content[0].text
570
- const featureMatch = text.match(/Feature: (.+)/)
571
- if (featureMatch) {
572
- activePath = featureMatch[1].trim()
573
- }
574
- mcpContext += `Spec CLI Context:\n${ToonEncoder.encode({ active_feature: activePath, status: text })}\n`
575
- }
576
- } catch (e) {
577
- log.debug("Failed to fetch spec status via mcpx", { error: String(e) })
578
- }
579
-
580
- try {
581
- log.debug("Fetching localized map from map for path via mcpx", { activePath })
582
- const mapRes = await mcpxTool.execute!(
583
- { server: "map", tool: "pm_query", flags: { path: activePath } },
584
- options as any,
585
- )
586
- if (mapRes.content && mapRes.content.length > 0 && mapRes.content[0].type === "text") {
587
- mcpContext += `Project Map Context:\n${ToonEncoder.encode({ localized_map: mapRes.content[0].text })}\n`
588
- }
589
- } catch (e) {
590
- log.debug("Failed to fetch map localized map via mcpx", { error: String(e) })
591
- }
592
- }
593
-
594
- if (mcpContext) {
595
- payload.zone1_critical_rules.push(mcpContext)
596
- }
597
- } catch (e) {
598
- log.warn("Phase 1 Pre-Generation MCP Context fetch failed", { error: String(e) })
599
- }
600
- }
601
-
602
- // Positional Prompt Architecture: Assemble Zone-based messages
603
- const initialMessages = PromptBuilder.buildMessages(payload, {
604
- isSmallReasoningModel,
605
- thinkingEffort: isReasoningModel ? thinkingEffort : undefined,
606
- })
607
- const systemContent = initialMessages
608
- .filter((m) => m.role === "system")
609
- .map((m) => m.content)
610
- .join("\n\n")
611
- const otherInitial = initialMessages.filter((m) => m.role !== "system")
612
-
613
- const internalStateCheck = `
614
- <|channel>thought
615
- [INTERNAL STATE CHECK]
616
- - Mode: ${thinkingEffort.toUpperCase()} thinking / Adaptive efficiency active.
617
- - Role: Assigned to [${input.agent.name.toUpperCase()}].
618
- - Constraint: ${Math.round(input.model.limit.context / 1000)}K token budget. Concise CoT.
619
- Ready to process user request strictly under these parameters.
620
- `.trim()
621
-
622
- // Truncate older proactive validation errors to prevent streaming loops and context bloat
623
- let validationErrorCount = 0
624
- for (let i = input.messages.length - 1; i >= 0; i--) {
625
- const msg = input.messages[i]
626
- if (msg.role === "tool" && Array.isArray(msg.content)) {
627
- for (let j = 0; j < msg.content.length; j++) {
628
- const part: any = msg.content[j]
629
- if (
630
- part.type === "tool-result" &&
631
- part.isError &&
632
- typeof part.result === "string" &&
633
- part.result.includes("INVALID ARGUMENTS:")
634
- ) {
635
- validationErrorCount++
636
- if (validationErrorCount > 2) {
637
- part.result = "INVALID ARGUMENTS: [TRUNCATED - Refer to most recent validation error]"
638
- }
639
- }
640
- }
641
- }
642
- }
643
-
644
- const messages = isOpenaiOauth
645
- ? input.messages
646
- : isWorkflow
647
- ? input.messages
648
- : mergeMessages([...otherInitial, ...input.messages, { role: "user" as const, content: internalStateCheck }])
649
-
650
- const params = await Plugin.trigger(
651
- "chat.params",
652
- {
653
- sessionID: input.sessionID,
654
- agent: input.agent.name,
655
- model: input.model,
656
- provider: providerInfo,
657
- message: input.user,
658
- },
659
- {
660
- temperature: input.model.capabilities.temperature
661
- ? (input.agent.temperature ?? ProviderTransform.temperature(input.model))
662
- : undefined,
663
- topP: input.agent.topP ?? ProviderTransform.topP(input.model),
664
- topK: ProviderTransform.topK(input.model),
665
- maxOutputTokens: ProviderTransform.maxOutputTokens(input.model),
666
- options,
667
- },
668
- )
669
-
670
- const { headers } = await Plugin.trigger(
671
- "chat.headers",
672
- {
673
- sessionID: input.sessionID,
674
- agent: input.agent.name,
675
- model: input.model,
676
- provider: providerInfo,
677
- message: input.user,
678
- },
679
- {
680
- headers: {},
681
- },
682
- )
683
-
684
- let resolvedTools = resolveTools(input)
685
-
686
- try {
687
- const availableSkills = await (await import("../skill")).Skill.available(input.agent)
688
- if (availableSkills.length === 0) {
689
- const { skill, ...rest } = resolvedTools as any
690
- resolvedTools = rest as any
691
- }
692
- } catch (e) {}
693
-
694
- const tools = Record.map(resolvedTools, (toolDef, toolName) => {
695
- if (!toolDef.execute) return toolDef
696
- const originalExecute = toolDef.execute
697
- return {
698
- ...toolDef,
699
- execute: async (args: any, options: any) => {
700
- const interception = await interceptToolLoop({
701
- toolName,
702
- args,
703
- messages: options.messages ?? input.messages,
704
- provider: providerInfo,
705
- cfg,
706
- })
707
- if (interception) return interception
708
-
709
- let result
710
- let isError = false
711
- try {
712
- result = await originalExecute(args, options)
713
- } catch (e: any) {
714
- result = e
715
- isError = true
716
- }
717
-
718
- const resultStr =
719
- typeof result === "string"
720
- ? result
721
- : result instanceof Error
722
- ? String(result.message || result)
723
- : JSON.stringify(result)
724
-
725
- // Task 1.1: Auto-Fallback for sc_guidance prerequisite
726
- if (
727
- resultStr.includes("You must run `spec sc_guidance`") ||
728
- resultStr.includes("You must run \\`spec sc_guidance\\`")
729
- ) {
730
- log.info("Auto-fallback triggered for missing prerequisite sc_guidance")
731
- let guidanceOutput = ""
732
- try {
733
- const allTools = resolveTools(input)
734
- const guidanceToolKey = Object.keys(allTools).find((k) => k.includes("sc_guidance"))
735
- if (guidanceToolKey && allTools[guidanceToolKey] && allTools[guidanceToolKey].execute) {
736
- const guidanceResult = await allTools[guidanceToolKey].execute!({}, options)
737
- guidanceOutput = typeof guidanceResult === "string" ? guidanceResult : JSON.stringify(guidanceResult)
738
- } else if (allTools["mcpx"] && allTools["mcpx"].execute) {
739
- const guidanceResult = await allTools["mcpx"].execute!(
740
- { server: "spec", tool: "sc_guidance", flags: {} },
741
- options,
742
- )
743
- guidanceOutput = typeof guidanceResult === "string" ? guidanceResult : JSON.stringify(guidanceResult)
744
- }
745
- } catch (fallbackError) {
746
- guidanceOutput = "Failed to auto-execute sc_guidance: " + String(fallbackError)
747
- }
748
-
749
- const hybridResponse =
750
- "System overriding sc_approve. Prerequisite missing. Auto-executing sc_guidance. Here is the guidance you must review... Read this, then you may call sc_approve.\n\n" +
751
- guidanceOutput
752
- if (isError) {
753
- return hybridResponse
754
- } else {
755
- return hybridResponse
756
- }
757
- }
758
-
759
- // Task 2.1 & 2.2: Clerk Interceptor (Middleware) for generic prerequisite errors
760
- if (isError && (resultStr.includes("You must run") || resultStr.includes("prerequisite"))) {
761
- if (providerInfo.id === "local-main") {
762
- try {
763
- log.info("Triggering Clerk Interceptor for prerequisite error")
764
- const sideModel = await Provider.getSideModel()
765
- if (sideModel) {
766
- const sideLanguage = await Provider.getLanguage(sideModel)
767
-
768
- // Build history string
769
- const tailCount = 5
770
- const recentMessages = (options.messages ?? input.messages).slice(-tailCount)
771
- const historyStr = recentMessages
772
- .map(
773
- (m: any) =>
774
- `${m.role}: ${typeof m.content === "string" ? m.content : JSON.stringify(m.content).slice(0, 200)}`,
775
- )
776
- .join("\n")
777
-
778
- const systemPrompt =
779
- "Look at the last tool error and the chat history. Write a concise, commanding one-sentence instruction telling the main agent exactly which tool to use next to resolve the prerequisite."
780
- const prompt = `History:\n${historyStr}\n\nTool Error:\n${resultStr}\n\nDirective:`
781
-
782
- const { generateText } = await import("@/util/ai-sdk")
783
- const res = await generateText({
784
- model: sideLanguage,
785
- system: systemPrompt,
786
- prompt: prompt,
787
- abortSignal: AbortSignal.timeout(10000),
788
- maxRetries: 0,
789
- })
790
-
791
- const clerkText = res.text.trim()
792
- if (clerkText) {
793
- log.info("Clerk interceptor generated directive", { directive: clerkText })
794
- throw new Error(`CRITICAL SYSTEM DIRECTIVE: ${clerkText}\n\nOriginal Error:\n${resultStr}`)
795
- }
796
- }
797
- } catch (clerkErr) {
798
- log.warn("Clerk interceptor failed", { error: String(clerkErr) })
799
- }
800
- }
801
- }
802
-
803
- if (isError) {
804
- // Task: Reactive Help Injection for mcpx syntax errors
805
- if (toolName === "mcpx" && args.server && args.tool) {
806
- try {
807
- const resultText = typeof result === "string" ? result : (result?.message || JSON.stringify(result))
808
- if (resultText.includes("Error (Exit") || resultText.includes("missing field") || resultText.includes("invalid type")) {
809
- log.info("mcpx syntax error detected, fetching help output", { server: args.server, tool: args.tool })
810
- const allTools = resolveTools(input)
811
- if (allTools["mcpx"] && allTools["mcpx"].execute) {
812
- const helpResult = await allTools["mcpx"].execute!({
813
- server: args.server,
814
- tool: args.tool,
815
- args: ["--help"]
816
- }, options)
817
-
818
- const helpOutput = typeof helpResult === "string" ? helpResult : (helpResult?.output ?? JSON.stringify(helpResult))
819
-
820
- let guidance = "The command failed with a syntax error. Review the correct schema below and retry with fixed arguments."
821
- if (resultText.includes("missing field title") && args.server === "spec") {
822
- guidance = "The 'spec' tool failed because Tasks.json is invalid. Specifically, it is missing the 'title' field in one or more task objects. Review your Tasks.json file, ensure 'title' is used instead of (or in addition to) 'description', and retry."
823
- } else if (resultText.includes("missing field id") && args.server === "spec" && args.tool === "sc_todo_start") {
824
- guidance = "The 'sc_todo_start' tool requires an '--id' flag. Additionally, ensure that the ID you are passing exactly matches the ID in Tasks.json, and that all IDs in Tasks.json follow a numeric-style format (e.g., '1', '1.1')."
825
- }
826
-
827
- const augmentedError = `${resultText}\n\n[SYSTEM GUIDANCE: ${guidance}]\n\n${helpOutput}`
828
-
829
- if (result instanceof Error) {
830
- result.message = augmentedError
831
- } else if (typeof result === "object") {
832
- result.output = augmentedError
833
- } else {
834
- result = augmentedError
835
- }
836
- }
837
- }
838
- } catch (helpErr) {
839
- log.warn("Failed to fetch reactive help for mcpx", { error: String(helpErr) })
840
- }
841
- }
842
- throw result
843
- }
844
- return result
845
- },
846
- }
847
- })
848
-
849
- // LiteLLM and some Anthropic proxies require the tools parameter to be present
850
- // when message history contains tool calls, even if no tools are being used.
851
- // Add a dummy tool that is never called to satisfy this validation.
852
- // This is enabled for:
853
- // 1. Providers with "litellm" in their ID or API ID (auto-detected)
854
- // 2. Providers with explicit "litellmProxy: true" option (opt-in for custom gateways)
855
- const isLiteLLMProxy =
856
- providerInfo.options?.["litellmProxy"] === true ||
857
- input.model.providerID.toLowerCase().includes("litellm") ||
858
- input.model.api.id.toLowerCase().includes("litellm")
859
-
860
- // LiteLLM/Bedrock rejects requests where the message history contains tool
861
- // calls but no tools param is present. When there are no active tools (e.g.
862
- // during compaction), inject a stub tool to satisfy the validation requirement.
863
- // The stub description explicitly tells the model not to call it.
864
- if (isLiteLLMProxy && Object.keys(tools).length === 0 && hasToolCalls(input.messages)) {
865
- tools["_noop"] = tool({
866
- description: "Do not call this tool. It exists only for API compatibility and must never be invoked.",
867
- inputSchema: jsonSchema({
868
- type: "object",
869
- properties: {
870
- reason: { type: "string", description: "Unused" },
871
- },
872
- }),
873
- execute: async () => ({ output: "", title: "", metadata: {} }),
874
- })
875
- }
876
-
877
- // Wire up toolExecutor for DWS workflow models so that tool calls
878
- // from the workflow service are executed via epochcli's tool system
879
- // and results sent back over the WebSocket.
880
- if (language instanceof GitLabWorkflowLanguageModel) {
881
- const workflowModel = language
882
- workflowModel.systemPrompt = system.join("\n")
883
- workflowModel.toolExecutor = async (toolName, argsJson, _requestID) => {
884
- const t = tools[toolName]
885
- if (!t || !t.execute) {
886
- return { result: "", error: `Unknown tool: ${toolName}` }
887
- }
888
- try {
889
- const result = await t.execute!(JSON.parse(argsJson), {
890
- toolCallId: _requestID,
891
- messages: input.messages,
892
- abortSignal: input.abort,
893
- })
894
- const output = typeof result === "string" ? result : (result?.output ?? JSON.stringify(result))
895
- return {
896
- result: output,
897
- metadata: typeof result === "object" ? result?.metadata : undefined,
898
- title: typeof result === "object" ? result?.title : undefined,
899
- }
900
- } catch (e: any) {
901
- return { result: "", error: e.message ?? String(e) }
902
- }
903
- }
904
- }
905
-
906
- if (providerInfo.id.startsWith("local-")) {
907
- await new Promise((r) => setTimeout(r, 2000))
908
- }
909
-
910
- return streamText({
911
- system: systemContent,
912
- onFinish(event) {
913
- // Worker is now handled sequentially in engine.ts
914
- },
915
- onError(error) {
916
- l.error("stream error", {
917
- error,
918
- })
919
- },
920
- async experimental_repairToolCall(failed) {
921
- // Phase 2 OutputInterceptor: Catch broken JSON from local-main and use local-side to fix it.
922
- if (providerInfo.id === "local-main") {
923
- try {
924
- const sideProviderConfig = cfg.provider?.["local-side"]
925
- if (sideProviderConfig) {
926
- log.debug("Attempting to repair broken JSON tool call with local-side Clerk")
927
- const sideModel = await Provider.getSideModel()
928
- if (!sideModel) return failed.toolCall
929
- const sideLanguage = await Provider.getLanguage(sideModel)
930
- const repairResponse = await generateText({
931
- model: sideLanguage,
932
- system:
933
- "You are a JSON repair utility. The user will provide a broken JSON tool call. Your ONLY job is to output the repaired, valid JSON object that matches the intended schema. DO NOT output any markdown, explanations, or other text. ONLY the valid JSON object.",
934
- prompt: `Broken JSON: ${(failed.toolCall as any).args}\n\nError: ${failed.error.message}`,
935
- abortSignal: AbortSignal.timeout(15000),
936
- maxRetries: 0,
937
- })
938
- try {
939
- const repairedArgs = JSON.parse(repairResponse.text.trim())
940
- log.info("Successfully repaired JSON with local-side")
941
-
942
- const repairEvent: Log.EnhancedModelExecutionEvent = {
943
- timestamp: Date.now(),
944
- mainEpochId: input.sessionID,
945
- event: "END_GENERATE",
946
- providerId: "local-side",
947
- phase: "Phase 2",
948
- metrics: { json_repaired: true },
949
- }
950
- l.info("json repair", repairEvent)
951
-
952
- return {
953
- ...failed.toolCall,
954
- args: repairedArgs,
955
- }
956
- } catch (parseErr) {
957
- log.warn("local-side failed to output valid JSON for repair")
958
- }
959
- }
960
- } catch (e) {
961
- log.error("Failed during local-side JSON repair attempt", { error: String(e) })
962
- }
963
- }
964
-
965
- const lower = failed.toolCall.toolName.toLowerCase()
966
- if (lower !== failed.toolCall.toolName && tools[lower]) {
967
- l.info("repairing tool call", {
968
- tool: failed.toolCall.toolName,
969
- repaired: lower,
970
- })
971
- return {
972
- ...failed.toolCall,
973
- toolName: lower,
974
- }
975
- }
976
- throw new Error(`Tool call failed: ${failed.error.message}`)
977
- },
978
- temperature: params.temperature,
979
- topP: params.topP,
980
- topK: params.topK,
981
- providerOptions: ProviderTransform.providerOptions(input.model, params.options),
982
- activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
983
- tools,
984
- toolChoice: input.toolChoice,
985
- maxOutputTokens: params.maxOutputTokens,
986
- abortSignal: input.abort,
987
- headers: {
988
- ...(input.model.providerID.startsWith("epochcli")
989
- ? {
990
- "x-epochcli-project": Instance.project.id,
991
- "x-epochcli-session": input.sessionID,
992
- "x-epochcli-request": input.user.id,
993
- "x-epochcli-client": Flag.EPOCHCLI_CLIENT,
994
- }
995
- : {
996
- "x-session-affinity": input.sessionID,
997
- ...(input.parentSessionID ? { "x-parent-session-id": input.parentSessionID } : {}),
998
- "User-Agent": `epochcli/${Installation.VERSION}`,
999
- }),
1000
- ...input.model.headers,
1001
- ...headers,
1002
- },
1003
- maxRetries: input.retries ?? 0,
1004
- messages,
1005
- model: wrapLanguageModel({
1006
- model: language,
1007
- middleware: [
1008
- {
1009
- specificationVersion: "v3" as const,
1010
- async transformParams(args) {
1011
- if (args.type === "stream" || args.type === "generate") {
1012
- const targetMessages = ProviderTransform.message(
1013
- args.params.prompt as ModelMessage[],
1014
- input.model,
1015
- options,
1016
- )
1017
- const targetKey = ProviderTransform.sdkKey(input.model.api.npm) ?? input.model.providerID
1018
- const isAzure = input.model.api.npm === "@ai-sdk/azure"
1019
- args.params.prompt = SanitizerMiddleware.stripProviderOptions(targetMessages, targetKey, isAzure)
1020
- }
1021
- return args.params
1022
- },
1023
- },
1024
- // Telemetry middleware
1025
- {
1026
- specificationVersion: "v3" as const,
1027
- wrapGenerate: async ({ doGenerate, params }) => {
1028
- const startTime = Date.now()
1029
- const truncatedPayload = Log.truncatePayload(params.prompt)
1030
- const mainEpochId = input.sessionID
1031
- const phase = input.model.providerID.includes("local-side") ? "Phase 1/3" : "Phase 2"
1032
-
1033
- const startEvent: Log.EnhancedModelExecutionEvent = {
1034
- timestamp: startTime,
1035
- mainEpochId,
1036
- event: "START_GENERATE",
1037
- providerId: input.model.providerID,
1038
- phase,
1039
- activeAgent: input.agent.name,
1040
- contextLimit: input.model.limit.context,
1041
- toolCount: Object.keys(tools).length,
1042
- payload: truncatedPayload,
1043
- tools: Flag.EPOCHCLI_DEBUG_FULL_PROMPT ? tools : undefined,
1044
- }
1045
- l.debug("model execution start", startEvent)
1046
- SessionTelemetry.emitModelEvent(startEvent)
1047
-
1048
- try {
1049
- const res = await doGenerate()
1050
- const endTime = Date.now()
1051
-
1052
- const endEvent: Log.EnhancedModelExecutionEvent = {
1053
- timestamp: endTime,
1054
- mainEpochId,
1055
- event: "END_GENERATE",
1056
- providerId: input.model.providerID,
1057
- phase,
1058
- activeAgent: input.agent.name,
1059
- toolCount: Object.keys(tools).length,
1060
- metrics: {
1061
- ttftMs: endTime - startTime,
1062
- promptTokens: (res.usage as any)?.promptTokens,
1063
- completionTokens: (res.usage as any)?.completionTokens,
1064
- tps: (res.usage as any)?.completionTokens
1065
- ? (res.usage as any).completionTokens / ((endTime - startTime) / 1000)
1066
- : undefined,
1067
- },
1068
- }
1069
- SessionTelemetry.emitModelEvent(endEvent)
1070
-
1071
- l.debug("model execution end", endEvent)
1072
- return res
1073
- } catch (e) {
1074
- const errorEvent: Log.EnhancedModelExecutionEvent = {
1075
- timestamp: Date.now(),
1076
- mainEpochId,
1077
- event: "ERROR",
1078
- providerId: input.model.providerID,
1079
- phase,
1080
- payload: { error: String(e) },
1081
- }
1082
- l.error("model execution error", errorEvent)
1083
- throw e
1084
- }
1085
- },
1086
- wrapStream: async ({ doStream, params }) => {
1087
- const startTime = Date.now()
1088
- const truncatedPayload = Log.truncatePayload(params.prompt)
1089
- const mainEpochId = input.sessionID
1090
- const phase = input.model.providerID.includes("local-side") ? "Phase 1/3" : "Phase 2"
1091
-
1092
- const startEvent: Log.EnhancedModelExecutionEvent = {
1093
- timestamp: startTime,
1094
- mainEpochId,
1095
- event: "START_GENERATE",
1096
- providerId: input.model.providerID,
1097
- phase,
1098
- activeAgent: input.agent.name,
1099
- contextLimit: input.model.limit.context,
1100
- toolCount: Object.keys(tools).length,
1101
- payload: truncatedPayload,
1102
- tools: Flag.EPOCHCLI_DEBUG_FULL_PROMPT ? tools : undefined,
1103
- }
1104
- l.debug("model execution start", startEvent)
1105
- SessionTelemetry.emitModelEvent(startEvent)
1106
-
1107
- try {
1108
- const { stream, ...rest } = await doStream()
1109
- let firstTokenTime: number | undefined
1110
- let tokenCount = 0
1111
- const monitor = new StreamingMonitor()
1112
-
1113
- const iterator = (async function* () {
1114
- for await (const chunk of stream as any) {
1115
- const textDelta = chunk.textDelta ?? chunk.delta
1116
- if (!firstTokenTime && chunk.type === "text-delta" && textDelta) {
1117
- firstTokenTime = Date.now()
1118
- }
1119
- if (chunk.type === "text-delta" || chunk.type === "tool-call-delta") {
1120
- tokenCount++
1121
- }
1122
-
1123
- if (chunk.type === "text-delta" && typeof textDelta === "string") {
1124
- if (monitor.push(textDelta)) {
1125
- const offendingText = monitor.getOffendingText()
1126
- l.warn("Streaming loop detected, aborting...", { offendingText })
1127
-
1128
- // Signal abortion to the provider if possible (though we only have the signal)
1129
- try {
1130
- ;(input.abort as any).dispatchEvent?.(new Event("abort"))
1131
- } catch (e) {}
1132
-
1133
- yield {
1134
- type: "text-delta",
1135
- textDelta: `\n\n[SYSTEM INTERVENTION: Thinking loop detected. Offending sequence: "${offendingText}". Generation aborted.]`,
1136
- delta: `\n\n[SYSTEM INTERVENTION: Thinking loop detected. Offending sequence: "${offendingText}". Generation aborted.]`,
1137
- } as any
1138
- return
1139
- }
1140
- }
1141
-
1142
- yield chunk
1143
- }
1144
- })()
1145
-
1146
- const readableStream = new ReadableStream({
1147
- async pull(controller) {
1148
- try {
1149
- const { value, done } = await iterator.next()
1150
- if (done) {
1151
- const endTime = Date.now()
1152
- const endEvent: Log.EnhancedModelExecutionEvent = {
1153
- timestamp: endTime,
1154
- mainEpochId,
1155
- event: "END_GENERATE",
1156
- providerId: input.model.providerID,
1157
- phase,
1158
- activeAgent: input.agent.name,
1159
- toolCount: Object.keys(tools).length,
1160
- metrics: {
1161
- ttftMs: firstTokenTime ? firstTokenTime - startTime : undefined,
1162
- tps:
1163
- tokenCount && firstTokenTime
1164
- ? tokenCount / ((endTime - firstTokenTime) / 1000)
1165
- : undefined,
1166
- promptTokens: (await (rest as any).usage)?.promptTokens,
1167
- completionTokens: (await (rest as any).usage)?.completionTokens,
1168
- loop_detected: monitor.getOffendingText() !== undefined,
1169
- },
1170
- }
1171
- l.debug("model execution end", endEvent)
1172
- controller.close()
1173
- } else {
1174
- controller.enqueue(value)
1175
- }
1176
- } catch (e) {
1177
- const errorEvent: Log.EnhancedModelExecutionEvent = {
1178
- timestamp: Date.now(),
1179
- mainEpochId,
1180
- event: "ERROR",
1181
- providerId: input.model.providerID,
1182
- phase,
1183
- payload: { error: String(e) },
1184
- }
1185
- l.error("model execution error", errorEvent)
1186
- controller.error(e)
1187
- }
1188
- },
1189
- cancel(reason) {
1190
- iterator.return?.(reason)
1191
- },
1192
- })
1193
-
1194
- return { stream: readableStream, ...rest }
1195
- } catch (e) {
1196
- const errorEvent: Log.EnhancedModelExecutionEvent = {
1197
- timestamp: Date.now(),
1198
- mainEpochId,
1199
- event: "ERROR",
1200
- providerId: input.model.providerID,
1201
- phase,
1202
- payload: { error: String(e) },
1203
- }
1204
- l.error("model execution error", errorEvent)
1205
- throw e
1206
- }
1207
- },
1208
- },
1209
- // NativeTokenParser middleware
1210
- {
1211
- specificationVersion: "v3" as const,
1212
- wrapGenerate: async ({ doGenerate, params }) => {
1213
- const res = await doGenerate()
1214
- // Replace <|"> with markdown backticks
1215
- ;(res as any).text = (res as any).text?.replace(/<\|">/g, "```")
1216
- return res
1217
- },
1218
- wrapStream: async ({ doStream, params }) => {
1219
- const { stream, ...rest } = await doStream()
1220
-
1221
- const iterator = (async function* () {
1222
- for await (const chunk of stream as any) {
1223
- if (chunk.type === "text-delta") {
1224
- if (typeof chunk.textDelta === "string") {
1225
- chunk.textDelta = chunk.textDelta.replace(/<\|">/g, "```")
1226
- }
1227
- if (typeof chunk.delta === "string") {
1228
- chunk.delta = chunk.delta.replace(/<\|">/g, "```")
1229
- }
1230
- }
1231
- yield chunk
1232
- }
1233
- })()
1234
-
1235
- const readableStream = new ReadableStream({
1236
- async pull(controller) {
1237
- const { value, done } = await iterator.next()
1238
- if (done) {
1239
- controller.close()
1240
- } else {
1241
- controller.enqueue(value)
1242
- }
1243
- },
1244
- cancel(reason) {
1245
- iterator.return?.(reason)
1246
- },
1247
- })
1248
-
1249
- return { stream: readableStream, ...rest }
1250
- },
1251
- },
1252
- ],
1253
- }),
1254
- experimental_telemetry: {
1255
- isEnabled: cfg.experimental?.openTelemetry,
1256
- metadata: {
1257
- userId: cfg.username ?? "unknown",
1258
- sessionId: input.sessionID,
1259
- },
1260
- },
1261
- })
1262
- }
1263
-
1264
- function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "permission" | "user">) {
1265
- const disabled = Permission.disabled(
1266
- Object.keys(input.tools),
1267
- Permission.merge(input.agent.permission, input.permission ?? []),
1268
- )
1269
- return Record.filter(input.tools, (_, k) => input.user.tools?.[k] !== false && !disabled.has(k))
1270
- }
1271
-
1272
- function mergeMessages(messages: ModelMessage[]): ModelMessage[] {
1273
- const result: ModelMessage[] = []
1274
- for (const msg of messages) {
1275
- const last = result[result.length - 1]
1276
- if (last && last.role === msg.role) {
1277
- if (typeof last.content === "string" && typeof msg.content === "string") {
1278
- last.content += "\n\n" + msg.content
1279
- continue
1280
- }
1281
- if (Array.isArray(last.content) && Array.isArray(msg.content)) {
1282
- ;(last.content as any[]).push(...msg.content)
1283
- continue
1284
- }
1285
- }
1286
- result.push(msg)
1287
- }
1288
- return result
1289
- }
1290
-
1291
- // Check if messages contain any tool-call content
1292
- // Used to determine if a dummy tool should be added for LiteLLM proxy compatibility
1293
- export function hasToolCalls(messages: ModelMessage[]): boolean {
1294
- for (const msg of messages) {
1295
- if (!Array.isArray(msg.content)) continue
1296
- for (const part of msg.content) {
1297
- if (part.type === "tool-call" || part.type === "tool-result") return true
1298
- }
1299
- }
1300
- return false
1301
- }
1302
-
1303
- /**
1304
- * Normalizes mcpx tool arguments (pos/flags) into a single object for validation.
1305
- */
1306
- export function normalizeMcpxArguments(args: any): Record<string, any> {
1307
- const normalized: Record<string, any> = {}
1308
-
1309
- // Extract from flags
1310
- if (args.flags && typeof args.flags === "object") {
1311
- Object.assign(normalized, args.flags)
1312
- }
1313
-
1314
- // Extract from args (heuristic for common patterns)
1315
- if (Array.isArray(args.args)) {
1316
- for (let i = 0; i < args.args.length; i++) {
1317
- const arg = args.args[i]
1318
- if (typeof arg === "string" && arg.startsWith("--")) {
1319
- const parts = arg.slice(2).split("=")
1320
- const key = parts[0]
1321
- const val = parts.length > 1 ? parts[1] : args.args[i + 1]
1322
- if (key) {
1323
- normalized[key] = val
1324
- if (parts.length === 1) i++ // skip next since it was used as value
1325
- }
1326
- }
1327
- }
1328
- }
1329
-
1330
- return normalized
1331
- }
1332
-
1333
- /**
1334
- * Performs a proactive validation turn with the Clerk.
1335
- */
1336
- async function validateArgumentsProactively(input: {
1337
- toolName: string
1338
- args: any
1339
- schema: any
1340
- provider: any
1341
- cfg: Config.Info
1342
- isMcpx?: boolean
1343
- mcpxServer?: string
1344
- l: any
1345
- }): Promise<string | null> {
1346
- try {
1347
- if (input.schema) {
1348
- // Attempt strict deterministic validation first to avoid LLM hallucinations
1349
- try {
1350
- const validate = ajv.compile(input.schema)
1351
- // For MCPX wrappers, the actual arguments to validate are nested
1352
- // The interceptToolLoop already attempts to unwrap them into validationArgs,
1353
- // but we ensure we are validating the correct payload against the sub-tool schema.
1354
- const payloadToValidate = input.isMcpx ? normalizeMcpxArguments(input.args) : input.args
1355
-
1356
- if (validate(payloadToValidate)) {
1357
- input.l.info("Deterministic schema validation passed", { tool: input.toolName })
1358
- return null // Valid, bypass side-model
1359
- }
1360
-
1361
- input.l.info("Deterministic schema validation failed, falling back to side-model for explanation", {
1362
- tool: input.toolName,
1363
- errors: validate.errors
1364
- })
1365
- } catch (e) {
1366
- input.l.warn("Failed to compile schema for deterministic validation", { error: String(e) })
1367
- // Fall through to side-model if we can't compile
1368
- }
1369
- }
1370
-
1371
- const sideModel = await Provider.getSideModel()
1372
- if (!sideModel) return null
1373
- const sideLanguage = await Provider.getLanguage(sideModel)
1374
-
1375
- const systemPrompt = input.isMcpx
1376
- ? `You are a tool argument validator. The user is attempting to call a sub-tool via the 'mcpx' tool wrapper. Compare the proposed SUB-TOOL arguments with the provided JSON schema.
1377
-
1378
- CRITICAL CONSTRAINTS:
1379
- 1. You MUST ONLY validate against the properties explicitly defined in the provided JSON schema.
1380
- 2. DO NOT invent new required properties or 'extra' fields. If a property is NOT in the schema, it is NOT required.
1381
- 3. If the proposed arguments satisfy the schema's 'required' array and property types, you MUST output 'VALID'.
1382
- 4. If they are invalid, output 'INVALID: <concise_reason> EXAMPLE: <strict_valid_json_example>'. You MUST explicitly explain what was missing or incorrect based SOLELY on the schema.
1383
-
1384
- TECHNICAL FORMATTING for 'mcpx' wrapper:
1385
- - Positional arguments (like command names or paths) MUST be passed in the 'args' array of strings.
1386
- - Named flags (like --path) should be in the 'flags' record.
1387
- - EXAMPLE for 'spec sc_status': \`{ "server": "spec", "tool": "sc_status", "args": [] }\`
1388
- - Your output example MUST be a JSON object containing "server", "tool", and "args" (and/or "flags").`
1389
- : `You are a tool argument validator. Compare the proposed arguments with the provided JSON schema.
1390
-
1391
- CRITICAL CONSTRAINTS:
1392
- 1. You MUST ONLY validate against the properties explicitly defined in the provided JSON schema.
1393
- 2. DO NOT invent new required properties, 'extra' fields, or metadata requirements. If a property is NOT in the schema, it is NOT required.
1394
- 3. If the proposed arguments satisfy the schema's 'required' array and property types, you MUST output 'VALID'.
1395
- 4. If they are invalid, output 'INVALID: <concise_reason> EXAMPLE: <strict_valid_json_example>'. You MUST explicitly explain what was missing or incorrect based SOLELY on the schema, and provide a strict, concrete JSON example of what the valid arguments should look like according to that schema.`
1396
-
1397
- const prompt = `Tool: ${input.toolName}\nProposed Args: ${JSON.stringify(input.args)}\nSchema: ${JSON.stringify(input.schema)}`
1398
-
1399
- const res = await generateText({
1400
- model: sideLanguage,
1401
- system: systemPrompt,
1402
- prompt: `${prompt}\n\nValidation Result:`,
1403
- abortSignal: AbortSignal.timeout(10000),
1404
- maxRetries: 0,
1405
- })
1406
-
1407
- const result = res.text.trim()
1408
- if (result.startsWith("INVALID")) {
1409
- return result.replace(/^INVALID:\s*/, "")
1410
- }
1411
- return null
1412
- } catch (e) {
1413
- input.l.warn("Proactive validation failed", { error: String(e) })
1414
- return null // Graceful fall-through
1415
- }
1416
- }
1417
-
1418
- export async function interceptToolLoop(input: {
1419
- toolName: string
1420
- args: any
1421
- messages: ModelMessage[]
1422
- provider: any
1423
- cfg: Config.Info
1424
- }) {
1425
- const l = log.clone()
1426
- l.error(`!!! DEBUG: interceptToolLoop called for ${input.toolName}. History length: ${input.messages.length}`)
1427
-
1428
- // Task: Proactive Validation (Clerk / local-side)
1429
- if (input.provider.id === "local-main") {
1430
- try {
1431
- let schemaToolName = input.toolName
1432
- let validationArgs = input.args
1433
-
1434
- let schema: any = undefined
1435
-
1436
- let isMcpx = false
1437
- let mcpxServer = ""
1438
-
1439
- // Specialized handling for mcpx sub-tools
1440
- if (input.toolName === "mcpx" && input.args.server && input.args.tool) {
1441
- isMcpx = true
1442
- mcpxServer = input.args.server
1443
- schemaToolName = input.args.tool
1444
- validationArgs = normalizeMcpxArguments(input.args)
1445
- schema = await SchemaContextLoader.getMcpxToolSchema(
1446
- input.args.server,
1447
- input.args.tool,
1448
- MCP.resolveMcpxBinary(input.cfg),
1449
- )
1450
- } else {
1451
- schema = await SchemaContextLoader.getToolSchema(schemaToolName)
1452
- }
1453
-
1454
- if (schema) {
1455
- log.info("Performing proactive validation", { tool: schemaToolName })
1456
- const hint = await validateArgumentsProactively({
1457
- toolName: schemaToolName,
1458
- args: validationArgs,
1459
- schema,
1460
- provider: input.provider,
1461
- cfg: input.cfg,
1462
- isMcpx,
1463
- mcpxServer,
1464
- l,
1465
- })
1466
-
1467
- if (hint) {
1468
- log.info("Proactive validation caught error", { tool: schemaToolName, hint })
1469
- return {
1470
- error: `INVALID ARGUMENTS: ${hint}`,
1471
- output: "",
1472
- title: "Argument Validation",
1473
- metadata: { schema_validated: true, proactive: true },
1474
- }
1475
- }
1476
- }
1477
- } catch (e) {
1478
- l.warn("Proactive validation turn failed", { error: String(e) })
1479
- }
1480
- }
1481
-
1482
- let identicalCount = 0
1483
- let globalSequentialFailureCount = 0
1484
- let identicalChainActive = true
1485
- let failureChainActive = true
1486
-
1487
- // For constructing the timeline prompt
1488
- const actionTimeline: string[] = []
1489
-
1490
- // Scan backwards through messages
1491
- for (let i = input.messages.length - 1; i >= 0; i--) {
1492
- if (!identicalChainActive && !failureChainActive && actionTimeline.length >= 8) break
1493
-
1494
- const msg = input.messages[i]
1495
-
1496
- if (msg.role === "assistant" && Array.isArray(msg.content)) {
1497
- for (const part of msg.content) {
1498
- if (part.type === "tool-call") {
1499
- const tName = (part as any).toolName || (part as any).name
1500
- const tArgs = (part as any).args
1501
- const tId = (part as any).toolCallId || (part as any).id
1502
-
1503
- let callResult = "unknown"
1504
- let callOutput = ""
1505
- let isErrorResult = false
1506
-
1507
- // Find corresponding tool result
1508
- const nextMsg = input.messages[i + 1]
1509
- if (nextMsg && nextMsg.role === "user" && Array.isArray(nextMsg.content)) {
1510
- const result = (nextMsg.content as any[]).find((c) => c.type === "tool-result" && c.toolCallId === tId)
1511
- if (result) {
1512
- isErrorResult = !!(result as any).isError
1513
- callResult = isErrorResult ? "error" : "completed"
1514
-
1515
- const outputVal = (result as any).output?.value || (result as any).output || ""
1516
- callOutput = typeof outputVal === "string" ? outputVal : JSON.stringify(outputVal)
1517
-
1518
- // Trim output for the timeline to prevent massive prompts
1519
- if (callOutput.length > 500) {
1520
- callOutput = callOutput.substring(0, 500) + "... [truncated]"
1521
- }
1522
- }
1523
- }
1524
-
1525
- // 1. Build Action Timeline (limit to 8)
1526
- if (actionTimeline.length < 8) {
1527
- actionTimeline.unshift(
1528
- `Tool '${tName}' executed (input: ${JSON.stringify(tArgs)}) -> Result: ${callResult} -> Output/Error: ${callOutput}`,
1529
- )
1530
- }
1531
-
1532
- // 2. Global Sequential Failures
1533
- if (failureChainActive) {
1534
- if (isErrorResult) {
1535
- globalSequentialFailureCount++
1536
- } else {
1537
- // Any successful tool call breaks the global failure chain
1538
- failureChainActive = false
1539
- }
1540
- }
1541
-
1542
- // 3. Identical arguments chain (only for the currently invoked tool)
1543
- if (tName === input.toolName) {
1544
- if (identicalChainActive) {
1545
- if (JSON.stringify(tArgs) === JSON.stringify(input.args)) {
1546
- identicalCount++
1547
- } else {
1548
- identicalChainActive = false
1549
- }
1550
- }
1551
- }
1552
- }
1553
- }
1554
- } else if (msg.role === "user" && typeof msg.content === "string") {
1555
- // Ignore internal state checks
1556
- if (!msg.content.includes("[INTERNAL STATE CHECK]")) {
1557
- // User interrupted or added new text
1558
- identicalChainActive = false
1559
- failureChainActive = false
1560
- }
1561
- }
1562
- }
1563
-
1564
- const isIdenticalLoop = identicalCount >= 1
1565
- const isFailureLoop = globalSequentialFailureCount >= 3
1566
-
1567
- // Schema-Aware Error Recovery (Task 2.2)
1568
- if (globalSequentialFailureCount >= 2 && !isIdenticalLoop && !isFailureLoop) {
1569
- try {
1570
- const schema = await SchemaContextLoader.getToolSchema(input.toolName)
1571
- if (schema && input.provider.id === "local-main") {
1572
- log.debug("Generating schema-aware correction hint with local-side Clerk")
1573
- const sideModel = await Provider.getSideModel()
1574
- if (!sideModel) return null
1575
- const sideLanguage = await Provider.getLanguage(sideModel)
1576
-
1577
- const systemPrompt =
1578
- "You are a tool argument validator. Compare the failed arguments with the provided JSON schema. Identify the mistake and provide a concise, helpful correction hint. Do not be verbose. Example: 'You are using --path, but sc_guidance accepts no arguments. Try calling it without flags.'"
1579
-
1580
- const prompt = `Tool: ${input.toolName}\nFailed Args: ${JSON.stringify(input.args)}\nSchema: ${JSON.stringify(schema)}`
1581
-
1582
- const hint = await generateText({
1583
- model: sideLanguage,
1584
- system: systemPrompt,
1585
- prompt: `${prompt}\n\nCorrection Hint:`,
1586
- abortSignal: AbortSignal.timeout(10000),
1587
- maxRetries: 0,
1588
- })
1589
-
1590
- return {
1591
- error: `INVALID ARGUMENTS: ${hint.text}`,
1592
- output: "",
1593
- title: "Argument Validation",
1594
- metadata: { schema_validated: true },
1595
- }
1596
- }
1597
- } catch (e) {
1598
- log.warn("Failed to generate schema-aware hint", { error: String(e) })
1599
- }
1600
- }
1601
-
1602
- if (isIdenticalLoop || isFailureLoop) {
1603
- const loopType = isIdenticalLoop ? "IDENTICAL_ARGS" : "SEQUENTIAL_FAILURES"
1604
- log.warn(`Loop detected for tool ${input.toolName}`, {
1605
- toolName: input.toolName,
1606
- args: input.args,
1607
- loopType,
1608
- globalSequentialFailureCount,
1609
- })
1610
-
1611
- // Use local-side to generate an intervention
1612
- if (input.provider.id === "local-main") {
1613
- try {
1614
- const sideProviderConfig = input.cfg.provider?.["local-side"]
1615
- if (sideProviderConfig) {
1616
- log.debug("Generating intervention directive with local-side Clerk")
1617
- const sideModel = await Provider.getSideModel()
1618
- if (!sideModel) return null
1619
- const sideLanguage = await Provider.getLanguage(sideModel)
1620
-
1621
- let continuityContext = ""
1622
- for (let i = 0; i < input.messages.length; i++) {
1623
- const m = input.messages[i]
1624
- if (
1625
- m.role === "user" &&
1626
- typeof m.content === "string" &&
1627
- m.content.includes("Hi, we are continuing a project as the context window ran out")
1628
- ) {
1629
- continuityContext = `\n\nContext (Epoch Continuity Report):\n${m.content}\n`
1630
- break
1631
- }
1632
- }
1633
-
1634
- const systemPrompt =
1635
- "You are an AI supervisor monitoring a main agent. The main agent is stuck in a doom loop or has stagnated. Analyze the recent failed attempts and the provided Action Timeline. Provide a concise, stern directive telling the agent to STOP calling this tool or repeating this behavior. Explain why its current approach is failing based on the error messages in the timeline, and suggest a specific actionable alternative strategy (like editing a file or using a different tool). Do not output anything other than the directive."
1636
-
1637
- const timelineStr = actionTimeline.map((a, idx) => `Turn ${idx + 1}: ${a}`).join("\n")
1638
-
1639
- const prompt = isIdenticalLoop
1640
- ? `Tool: ${input.toolName}\nArgs: ${JSON.stringify(input.args)}\nStatus: Stuck in an infinite loop with identical arguments.${continuityContext}\n\nRecent Action Timeline:\n${timelineStr}`
1641
- : `Tool: ${input.toolName}\nStatus: Stuck in a trial-and-error loop where all recent attempts have failed.${continuityContext}\n\nRecent Action Timeline:\n${timelineStr}`
1642
-
1643
- const intervention = await generateText({
1644
- model: sideLanguage,
1645
- system: systemPrompt,
1646
- prompt: `${prompt}\n\nPlease provide the intervention directive:`,
1647
- abortSignal: AbortSignal.timeout(15000),
1648
- maxRetries: 0,
1649
- })
1650
-
1651
- const interventionEvent: Log.EnhancedModelExecutionEvent = {
1652
- timestamp: Date.now(),
1653
- mainEpochId: "test", // placeholder, will be real in stream()
1654
- event: "END_GENERATE",
1655
- providerId: "local-side",
1656
- phase: "Phase 2",
1657
- metrics: { loop_detected: true, loop_type: loopType },
1658
- }
1659
- log.info("intervention", interventionEvent)
1660
-
1661
- return { error: `SYSTEM INTERVENTION: ${intervention.text}`, output: "", title: "", metadata: {} }
1662
- }
1663
- } catch (e) {
1664
- log.error("Failed to generate intervention with local-side", { error: String(e) })
1665
- }
1666
- }
1667
- return {
1668
- error: `SYSTEM INTERVENTION: You are stuck in a ${isIdenticalLoop ? "loop calling this tool with the exact same arguments" : "repeated failure loop with this tool"}. Stop and reconsider your approach.`,
1669
- output: "",
1670
- title: "",
1671
- metadata: {},
1672
- }
1673
- }
1674
- return null
1675
- }
1676
- }