@epoch-ai/cli 2.2.4

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 (710) hide show
  1. package/.artifacts/unit/junit.xml +2823 -0
  2. package/.project-map/backups/20260530_223453/.project-map.json +90101 -0
  3. package/.project-map/backups/20260530_223507/.project-map.json +90101 -0
  4. package/.project-map/backups/20260530_223512/.project-map.json +90101 -0
  5. package/.project-map/backups/20260530_223512/map.toon +666 -0
  6. package/.project-map/backups/20260530_223516/.project-map.json +90101 -0
  7. package/.project-map/backups/20260530_223516/map.toon +666 -0
  8. package/.project-map/backups/20260530_223520/.project-map.json +90101 -0
  9. package/.project-map/backups/20260530_223520/map.toon +666 -0
  10. package/AGENTS.md +47 -0
  11. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  12. package/Dockerfile +18 -0
  13. package/README.md +15 -0
  14. package/bin/epochcli +179 -0
  15. package/bunfig.toml +7 -0
  16. package/drizzle.config.ts +10 -0
  17. package/git +0 -0
  18. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  19. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  20. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  21. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  22. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  23. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  24. package/migration/20260225215848_workspace/migration.sql +7 -0
  25. package/migration/20260225215848_workspace/snapshot.json +959 -0
  26. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  27. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  28. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  29. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  30. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  31. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  32. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  33. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  34. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  35. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  36. package/migration/20260323234822_events/migration.sql +13 -0
  37. package/migration/20260323234822_events/snapshot.json +1271 -0
  38. package/migration/20260418092949_add_yolo_to_session/migration.sql +2 -0
  39. package/migration/20260418092949_add_yolo_to_session/snapshot.json +1199 -0
  40. package/migration/20260419120000_add_intervention_to_session/migration.sql +2 -0
  41. package/package.json +186 -0
  42. package/parsers-config.ts +290 -0
  43. package/script/build-node.ts +71 -0
  44. package/script/build.ts +255 -0
  45. package/script/check-migrations.ts +16 -0
  46. package/script/fix-node-pty.ts +28 -0
  47. package/script/postinstall.mjs +131 -0
  48. package/script/publish.ts +184 -0
  49. package/script/schema.ts +63 -0
  50. package/script/seed-e2e.ts +60 -0
  51. package/script/upgrade-opentui.ts +64 -0
  52. package/specs/effect-migration.md +310 -0
  53. package/specs/tui-plugins.md +436 -0
  54. package/specs/v2.md +14 -0
  55. package/src/account/account.sql.ts +39 -0
  56. package/src/account/index.ts +465 -0
  57. package/src/account/repo.ts +163 -0
  58. package/src/account/schema.ts +91 -0
  59. package/src/acp/README.md +174 -0
  60. package/src/acp/agent.ts +1847 -0
  61. package/src/acp/session.ts +116 -0
  62. package/src/acp/types.ts +24 -0
  63. package/src/agent/agent.ts +445 -0
  64. package/src/agent/generate.txt +75 -0
  65. package/src/agent/prompt/compaction.txt +15 -0
  66. package/src/agent/prompt/explore.txt +9 -0
  67. package/src/agent/prompt/summary.txt +11 -0
  68. package/src/agent/prompt/title.txt +44 -0
  69. package/src/auth/index.ts +110 -0
  70. package/src/bus/bus-event.ts +40 -0
  71. package/src/bus/global.ts +10 -0
  72. package/src/bus/index.ts +232 -0
  73. package/src/cli/bootstrap.ts +17 -0
  74. package/src/cli/cmd/account.ts +257 -0
  75. package/src/cli/cmd/acp.ts +70 -0
  76. package/src/cli/cmd/agent.ts +245 -0
  77. package/src/cli/cmd/cmd.ts +7 -0
  78. package/src/cli/cmd/db.ts +119 -0
  79. package/src/cli/cmd/debug/agent.ts +167 -0
  80. package/src/cli/cmd/debug/config.ts +16 -0
  81. package/src/cli/cmd/debug/file.ts +97 -0
  82. package/src/cli/cmd/debug/index.ts +48 -0
  83. package/src/cli/cmd/debug/lsp.ts +53 -0
  84. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  85. package/src/cli/cmd/debug/scrap.ts +16 -0
  86. package/src/cli/cmd/debug/skill.ts +16 -0
  87. package/src/cli/cmd/debug/snapshot.ts +52 -0
  88. package/src/cli/cmd/export.ts +89 -0
  89. package/src/cli/cmd/generate.ts +38 -0
  90. package/src/cli/cmd/github.ts +1639 -0
  91. package/src/cli/cmd/import.ts +169 -0
  92. package/src/cli/cmd/mcp.ts +754 -0
  93. package/src/cli/cmd/models.ts +78 -0
  94. package/src/cli/cmd/plug.ts +233 -0
  95. package/src/cli/cmd/pr.ts +127 -0
  96. package/src/cli/cmd/providers.ts +478 -0
  97. package/src/cli/cmd/run.ts +681 -0
  98. package/src/cli/cmd/serve.ts +24 -0
  99. package/src/cli/cmd/session.ts +159 -0
  100. package/src/cli/cmd/stats.ts +410 -0
  101. package/src/cli/cmd/tui/app.tsx +945 -0
  102. package/src/cli/cmd/tui/attach.ts +88 -0
  103. package/src/cli/cmd/tui/component/border.tsx +21 -0
  104. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  105. package/src/cli/cmd/tui/component/dialog-command.tsx +171 -0
  106. package/src/cli/cmd/tui/component/dialog-console-org.tsx +103 -0
  107. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  108. package/src/cli/cmd/tui/component/dialog-model.tsx +190 -0
  109. package/src/cli/cmd/tui/component/dialog-provider.tsx +364 -0
  110. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  111. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  112. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  113. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  114. package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
  115. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  116. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  117. package/src/cli/cmd/tui/component/dialog-variant.tsx +39 -0
  118. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +320 -0
  119. package/src/cli/cmd/tui/component/error-component.tsx +92 -0
  120. package/src/cli/cmd/tui/component/logo.tsx +85 -0
  121. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  122. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +672 -0
  123. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  124. package/src/cli/cmd/tui/component/prompt/history.tsx +109 -0
  125. package/src/cli/cmd/tui/component/prompt/index.tsx +1336 -0
  126. package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
  127. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  128. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  129. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  130. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  131. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  132. package/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx +151 -0
  133. package/src/cli/cmd/tui/context/args.tsx +15 -0
  134. package/src/cli/cmd/tui/context/directory.ts +13 -0
  135. package/src/cli/cmd/tui/context/exit.tsx +60 -0
  136. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  137. package/src/cli/cmd/tui/context/keybind.tsx +105 -0
  138. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  139. package/src/cli/cmd/tui/context/local.tsx +456 -0
  140. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  141. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  142. package/src/cli/cmd/tui/context/route.tsx +52 -0
  143. package/src/cli/cmd/tui/context/sdk.tsx +115 -0
  144. package/src/cli/cmd/tui/context/sync.tsx +516 -0
  145. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  146. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  147. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  148. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  149. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  150. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  151. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  152. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  153. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  154. package/src/cli/cmd/tui/context/theme/epochcli.json +245 -0
  155. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  156. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  157. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  158. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  159. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  160. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  161. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  162. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  163. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  164. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  165. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  166. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  167. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  168. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  169. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  170. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  171. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  172. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  173. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  174. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  175. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  176. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  177. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  178. package/src/cli/cmd/tui/context/theme.tsx +1236 -0
  179. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  180. package/src/cli/cmd/tui/event.ts +48 -0
  181. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +93 -0
  182. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +145 -0
  183. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
  184. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
  185. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  186. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
  187. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  188. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
  189. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
  190. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +270 -0
  191. package/src/cli/cmd/tui/plugin/api.tsx +397 -0
  192. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  193. package/src/cli/cmd/tui/plugin/internal.ts +27 -0
  194. package/src/cli/cmd/tui/plugin/runtime.ts +1031 -0
  195. package/src/cli/cmd/tui/plugin/slots.tsx +60 -0
  196. package/src/cli/cmd/tui/routes/home.tsx +84 -0
  197. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
  198. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
  199. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  200. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  201. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  202. package/src/cli/cmd/tui/routes/session/index.tsx +2161 -0
  203. package/src/cli/cmd/tui/routes/session/permission.tsx +691 -0
  204. package/src/cli/cmd/tui/routes/session/question.tsx +468 -0
  205. package/src/cli/cmd/tui/routes/session/sidebar.tsx +70 -0
  206. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +131 -0
  207. package/src/cli/cmd/tui/thread.ts +241 -0
  208. package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
  209. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +89 -0
  210. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +211 -0
  211. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  212. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +115 -0
  213. package/src/cli/cmd/tui/ui/dialog-select.tsx +417 -0
  214. package/src/cli/cmd/tui/ui/dialog.tsx +192 -0
  215. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  216. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  217. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  218. package/src/cli/cmd/tui/util/clipboard.ts +192 -0
  219. package/src/cli/cmd/tui/util/editor.ts +37 -0
  220. package/src/cli/cmd/tui/util/model.ts +23 -0
  221. package/src/cli/cmd/tui/util/provider-origin.ts +20 -0
  222. package/src/cli/cmd/tui/util/scroll.ts +23 -0
  223. package/src/cli/cmd/tui/util/selection.ts +25 -0
  224. package/src/cli/cmd/tui/util/signal.ts +7 -0
  225. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  226. package/src/cli/cmd/tui/util/transcript.ts +112 -0
  227. package/src/cli/cmd/tui/win32.ts +129 -0
  228. package/src/cli/cmd/tui/worker.ts +195 -0
  229. package/src/cli/cmd/uninstall.ts +353 -0
  230. package/src/cli/cmd/upgrade.ts +73 -0
  231. package/src/cli/cmd/web.ts +81 -0
  232. package/src/cli/effect/prompt.ts +25 -0
  233. package/src/cli/error.ts +46 -0
  234. package/src/cli/heap.ts +59 -0
  235. package/src/cli/logo.ts +6 -0
  236. package/src/cli/network.ts +60 -0
  237. package/src/cli/ui.ts +133 -0
  238. package/src/cli/upgrade.ts +31 -0
  239. package/src/command/index.ts +197 -0
  240. package/src/command/template/initialize.txt +66 -0
  241. package/src/command/template/review.txt +101 -0
  242. package/src/config/config.ts +1610 -0
  243. package/src/config/console-state.ts +15 -0
  244. package/src/config/markdown.ts +99 -0
  245. package/src/config/paths.ts +167 -0
  246. package/src/config/tui-migrate.ts +155 -0
  247. package/src/config/tui-schema.ts +37 -0
  248. package/src/config/tui.ts +179 -0
  249. package/src/config/validator.ts +52 -0
  250. package/src/control-plane/adaptors/index.ts +20 -0
  251. package/src/control-plane/adaptors/worktree.ts +42 -0
  252. package/src/control-plane/schema.ts +17 -0
  253. package/src/control-plane/sse.ts +66 -0
  254. package/src/control-plane/types.ts +32 -0
  255. package/src/control-plane/workspace.sql.ts +17 -0
  256. package/src/control-plane/workspace.ts +168 -0
  257. package/src/effect/cross-spawn-spawner.ts +502 -0
  258. package/src/effect/instance-ref.ts +6 -0
  259. package/src/effect/instance-registry.ts +12 -0
  260. package/src/effect/instance-state.ts +82 -0
  261. package/src/effect/run-service.ts +33 -0
  262. package/src/effect/runner.ts +216 -0
  263. package/src/env/index.ts +28 -0
  264. package/src/file/ignore.ts +82 -0
  265. package/src/file/index.ts +686 -0
  266. package/src/file/protected.ts +59 -0
  267. package/src/file/ripgrep.ts +376 -0
  268. package/src/file/time.ts +133 -0
  269. package/src/file/watcher.ts +172 -0
  270. package/src/filesystem/index.ts +236 -0
  271. package/src/flag/flag.ts +157 -0
  272. package/src/format/formatter.ts +413 -0
  273. package/src/format/index.ts +203 -0
  274. package/src/git/index.ts +303 -0
  275. package/src/global/index.ts +54 -0
  276. package/src/id/id.ts +85 -0
  277. package/src/ide/index.ts +74 -0
  278. package/src/index.ts +253 -0
  279. package/src/installation/index.ts +355 -0
  280. package/src/installation/meta.ts +7 -0
  281. package/src/lsp/client.ts +256 -0
  282. package/src/lsp/index.ts +558 -0
  283. package/src/lsp/language.ts +120 -0
  284. package/src/lsp/launch.ts +21 -0
  285. package/src/lsp/server.ts +1968 -0
  286. package/src/mcp/auth.ts +173 -0
  287. package/src/mcp/index.ts +1250 -0
  288. package/src/mcp/oauth-callback.ts +216 -0
  289. package/src/mcp/oauth-provider.ts +185 -0
  290. package/src/mcp/schema-loader.ts +82 -0
  291. package/src/node.ts +1 -0
  292. package/src/npm/index.ts +188 -0
  293. package/src/patch/index.ts +680 -0
  294. package/src/permission/arity.ts +163 -0
  295. package/src/permission/evaluate.ts +15 -0
  296. package/src/permission/index.ts +323 -0
  297. package/src/permission/schema.ts +17 -0
  298. package/src/plugin/cloudflare.ts +67 -0
  299. package/src/plugin/codex.ts +608 -0
  300. package/src/plugin/github-copilot/copilot.ts +361 -0
  301. package/src/plugin/github-copilot/models.ts +144 -0
  302. package/src/plugin/index.ts +288 -0
  303. package/src/plugin/install.ts +439 -0
  304. package/src/plugin/loader.ts +174 -0
  305. package/src/plugin/meta.ts +188 -0
  306. package/src/plugin/shared.ts +323 -0
  307. package/src/project/bootstrap.ts +29 -0
  308. package/src/project/instance.ts +175 -0
  309. package/src/project/project.sql.ts +16 -0
  310. package/src/project/project.ts +519 -0
  311. package/src/project/schema.ts +16 -0
  312. package/src/project/state.ts +70 -0
  313. package/src/project/vcs.ts +240 -0
  314. package/src/provider/auth.ts +253 -0
  315. package/src/provider/error.ts +297 -0
  316. package/src/provider/models.ts +162 -0
  317. package/src/provider/provider.ts +1776 -0
  318. package/src/provider/schema.ts +38 -0
  319. package/src/provider/sdk/copilot/README.md +5 -0
  320. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  321. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  322. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  323. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  324. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +814 -0
  325. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  326. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  327. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  328. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  329. package/src/provider/sdk/copilot/index.ts +2 -0
  330. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  331. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
  332. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  333. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  334. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  335. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
  336. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1769 -0
  337. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
  338. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  339. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
  340. package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
  341. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
  342. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
  343. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
  344. package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
  345. package/src/provider/transform.ts +1124 -0
  346. package/src/pty/index.ts +397 -0
  347. package/src/pty/pty.bun.ts +26 -0
  348. package/src/pty/pty.node.ts +27 -0
  349. package/src/pty/pty.ts +25 -0
  350. package/src/pty/schema.ts +17 -0
  351. package/src/question/index.ts +224 -0
  352. package/src/question/schema.ts +17 -0
  353. package/src/server/error.ts +36 -0
  354. package/src/server/event.ts +7 -0
  355. package/src/server/instance.ts +315 -0
  356. package/src/server/mdns.ts +60 -0
  357. package/src/server/middleware.ts +33 -0
  358. package/src/server/projectors.ts +28 -0
  359. package/src/server/proxy.ts +130 -0
  360. package/src/server/router.ts +105 -0
  361. package/src/server/routes/config.ts +92 -0
  362. package/src/server/routes/event.ts +83 -0
  363. package/src/server/routes/experimental.ts +374 -0
  364. package/src/server/routes/file.ts +197 -0
  365. package/src/server/routes/global.ts +312 -0
  366. package/src/server/routes/mcp.ts +225 -0
  367. package/src/server/routes/permission.ts +69 -0
  368. package/src/server/routes/project.ts +118 -0
  369. package/src/server/routes/provider.ts +171 -0
  370. package/src/server/routes/pty.ts +210 -0
  371. package/src/server/routes/question.ts +99 -0
  372. package/src/server/routes/session.ts +984 -0
  373. package/src/server/routes/tui.ts +378 -0
  374. package/src/server/routes/workspace.ts +94 -0
  375. package/src/server/server.ts +353 -0
  376. package/src/session/compaction.ts +86 -0
  377. package/src/session/index.ts +904 -0
  378. package/src/session/instruction.ts +261 -0
  379. package/src/session/llm/monitor.ts +87 -0
  380. package/src/session/llm.ts +1676 -0
  381. package/src/session/message-v2.ts +1082 -0
  382. package/src/session/message.ts +191 -0
  383. package/src/session/overflow.ts +35 -0
  384. package/src/session/processor.ts +635 -0
  385. package/src/session/projectors.ts +136 -0
  386. package/src/session/prompt/build-switch.txt +5 -0
  387. package/src/session/prompt/builder.ts +135 -0
  388. package/src/session/prompt/default.txt +11 -0
  389. package/src/session/prompt/engine.ts +1072 -0
  390. package/src/session/prompt/gemma4.txt +1 -0
  391. package/src/session/prompt/max-steps.txt +16 -0
  392. package/src/session/prompt/orchestrator.ts +426 -0
  393. package/src/session/prompt/plan.txt +28 -0
  394. package/src/session/prompt/qwen.txt +19 -0
  395. package/src/session/prompt/resolver.ts +670 -0
  396. package/src/session/prompt/router.ts +197 -0
  397. package/src/session/prompt/state.ts +96 -0
  398. package/src/session/prompt/types.ts +115 -0
  399. package/src/session/prompt/utils.ts +15 -0
  400. package/src/session/prompt.ts +362 -0
  401. package/src/session/retry.ts +106 -0
  402. package/src/session/revert.ts +176 -0
  403. package/src/session/sanitizer.ts +125 -0
  404. package/src/session/schema.ts +38 -0
  405. package/src/session/session.sql.ts +106 -0
  406. package/src/session/status.ts +102 -0
  407. package/src/session/summary.ts +183 -0
  408. package/src/session/system.ts +79 -0
  409. package/src/session/todo.ts +166 -0
  410. package/src/session/worker.ts +382 -0
  411. package/src/shell/shell.ts +110 -0
  412. package/src/skill/discovery.ts +116 -0
  413. package/src/skill/index.ts +287 -0
  414. package/src/snapshot/index.ts +726 -0
  415. package/src/sql.d.ts +4 -0
  416. package/src/storage/db.bun.ts +8 -0
  417. package/src/storage/db.node.ts +8 -0
  418. package/src/storage/db.ts +174 -0
  419. package/src/storage/json-migration.ts +387 -0
  420. package/src/storage/schema.sql.ts +10 -0
  421. package/src/storage/schema.ts +4 -0
  422. package/src/storage/storage.ts +353 -0
  423. package/src/sync/README.md +179 -0
  424. package/src/sync/event.sql.ts +16 -0
  425. package/src/sync/index.ts +263 -0
  426. package/src/sync/schema.ts +14 -0
  427. package/src/tool/apply_patch.ts +281 -0
  428. package/src/tool/apply_patch.txt +1 -0
  429. package/src/tool/arbitration.txt +5 -0
  430. package/src/tool/bash.ts +494 -0
  431. package/src/tool/bash.txt +2 -0
  432. package/src/tool/batch.ts +183 -0
  433. package/src/tool/batch.txt +1 -0
  434. package/src/tool/codesearch.ts +132 -0
  435. package/src/tool/codesearch.txt +1 -0
  436. package/src/tool/edit.ts +734 -0
  437. package/src/tool/edit.txt +1 -0
  438. package/src/tool/external-directory.ts +46 -0
  439. package/src/tool/glob.ts +73 -0
  440. package/src/tool/glob.txt +2 -0
  441. package/src/tool/grep.ts +156 -0
  442. package/src/tool/grep.txt +2 -0
  443. package/src/tool/invalid.ts +20 -0
  444. package/src/tool/ls.ts +121 -0
  445. package/src/tool/ls.txt +1 -0
  446. package/src/tool/lsp.ts +97 -0
  447. package/src/tool/lsp.txt +1 -0
  448. package/src/tool/multiedit.ts +46 -0
  449. package/src/tool/multiedit.txt +1 -0
  450. package/src/tool/plan-enter.txt +14 -0
  451. package/src/tool/plan-exit.txt +13 -0
  452. package/src/tool/plan.ts +131 -0
  453. package/src/tool/question.ts +46 -0
  454. package/src/tool/question.txt +10 -0
  455. package/src/tool/read.ts +332 -0
  456. package/src/tool/read.txt +1 -0
  457. package/src/tool/registry.ts +288 -0
  458. package/src/tool/revert.ts +37 -0
  459. package/src/tool/schema.ts +17 -0
  460. package/src/tool/skill.ts +105 -0
  461. package/src/tool/task.ts +150 -0
  462. package/src/tool/task.txt +3 -0
  463. package/src/tool/task_complete.ts +21 -0
  464. package/src/tool/tool.ts +112 -0
  465. package/src/tool/truncate.ts +144 -0
  466. package/src/tool/truncation-dir.ts +4 -0
  467. package/src/tool/webfetch.ts +206 -0
  468. package/src/tool/webfetch.txt +1 -0
  469. package/src/tool/websearch.ts +150 -0
  470. package/src/tool/websearch.txt +1 -0
  471. package/src/tool/write.ts +101 -0
  472. package/src/tool/write.txt +1 -0
  473. package/src/util/abort.ts +35 -0
  474. package/src/util/ai-sdk.ts +59 -0
  475. package/src/util/archive.ts +17 -0
  476. package/src/util/color.ts +19 -0
  477. package/src/util/context.ts +25 -0
  478. package/src/util/data-url.ts +9 -0
  479. package/src/util/defer.ts +12 -0
  480. package/src/util/effect-http-client.ts +11 -0
  481. package/src/util/effect-zod.ts +98 -0
  482. package/src/util/error.ts +77 -0
  483. package/src/util/filesystem.ts +245 -0
  484. package/src/util/flock.ts +333 -0
  485. package/src/util/fn.ts +21 -0
  486. package/src/util/format.ts +20 -0
  487. package/src/util/glob.ts +34 -0
  488. package/src/util/hash.ts +7 -0
  489. package/src/util/iife.ts +3 -0
  490. package/src/util/keybind.ts +103 -0
  491. package/src/util/lazy.ts +23 -0
  492. package/src/util/locale.ts +81 -0
  493. package/src/util/lock.ts +98 -0
  494. package/src/util/log-parser.ts +114 -0
  495. package/src/util/log.ts +250 -0
  496. package/src/util/network.ts +23 -0
  497. package/src/util/process.ts +176 -0
  498. package/src/util/queue.ts +32 -0
  499. package/src/util/record.ts +3 -0
  500. package/src/util/rpc.ts +66 -0
  501. package/src/util/schema.ts +53 -0
  502. package/src/util/scrap.ts +10 -0
  503. package/src/util/session-analyzer.ts +331 -0
  504. package/src/util/session-telemetry.ts +91 -0
  505. package/src/util/signal.ts +12 -0
  506. package/src/util/timeout.ts +14 -0
  507. package/src/util/token.ts +7 -0
  508. package/src/util/tokenizer.ts +50 -0
  509. package/src/util/toon.ts +45 -0
  510. package/src/util/update-schema.ts +13 -0
  511. package/src/util/which.ts +14 -0
  512. package/src/util/wildcard.ts +59 -0
  513. package/src/worktree/index.ts +612 -0
  514. package/sst-env.d.ts +10 -0
  515. package/test/AGENTS.md +81 -0
  516. package/test/account/repo.test.ts +326 -0
  517. package/test/account/service.test.ts +393 -0
  518. package/test/acp/agent-interface.test.ts +51 -0
  519. package/test/acp/event-subscription.test.ts +685 -0
  520. package/test/agent/agent.test.ts +716 -0
  521. package/test/auth/auth.test.ts +58 -0
  522. package/test/bus/bus-effect.test.ts +164 -0
  523. package/test/bus/bus-integration.test.ts +87 -0
  524. package/test/bus/bus.test.ts +219 -0
  525. package/test/cli/account.test.ts +26 -0
  526. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  527. package/test/cli/github-action.test.ts +198 -0
  528. package/test/cli/github-remote.test.ts +80 -0
  529. package/test/cli/plugin-auth-picker.test.ts +120 -0
  530. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  531. package/test/cli/tui/plugin-add.test.ts +107 -0
  532. package/test/cli/tui/plugin-install.test.ts +89 -0
  533. package/test/cli/tui/plugin-lifecycle.test.ts +225 -0
  534. package/test/cli/tui/plugin-loader-entrypoint.test.ts +492 -0
  535. package/test/cli/tui/plugin-loader-pure.test.ts +72 -0
  536. package/test/cli/tui/plugin-loader.test.ts +752 -0
  537. package/test/cli/tui/plugin-toggle.test.ts +159 -0
  538. package/test/cli/tui/slot-replace.test.tsx +47 -0
  539. package/test/cli/tui/theme-store.test.ts +51 -0
  540. package/test/cli/tui/thread.test.ts +128 -0
  541. package/test/cli/tui/transcript.test.ts +426 -0
  542. package/test/config/agent-color.test.ts +71 -0
  543. package/test/config/config.test.ts +2337 -0
  544. package/test/config/fixtures/empty-frontmatter.md +4 -0
  545. package/test/config/fixtures/frontmatter.md +28 -0
  546. package/test/config/fixtures/markdown-header.md +11 -0
  547. package/test/config/fixtures/no-frontmatter.md +1 -0
  548. package/test/config/fixtures/weird-model-id.md +13 -0
  549. package/test/config/markdown.test.ts +228 -0
  550. package/test/config/tui.test.ts +800 -0
  551. package/test/control-plane/sse.test.ts +56 -0
  552. package/test/effect/cross-spawn-spawner.test.ts +412 -0
  553. package/test/effect/instance-state.test.ts +482 -0
  554. package/test/effect/run-service.test.ts +46 -0
  555. package/test/effect/runner.test.ts +523 -0
  556. package/test/fake/provider.ts +82 -0
  557. package/test/file/fsmonitor.test.ts +62 -0
  558. package/test/file/ignore.test.ts +10 -0
  559. package/test/file/index.test.ts +946 -0
  560. package/test/file/path-traversal.test.ts +198 -0
  561. package/test/file/ripgrep.test.ts +54 -0
  562. package/test/file/time.test.ts +445 -0
  563. package/test/file/watcher.test.ts +247 -0
  564. package/test/filesystem/filesystem.test.ts +319 -0
  565. package/test/fixture/db.ts +11 -0
  566. package/test/fixture/fixture.test.ts +26 -0
  567. package/test/fixture/fixture.ts +172 -0
  568. package/test/fixture/flock-worker.ts +72 -0
  569. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  570. package/test/fixture/plug-worker.ts +93 -0
  571. package/test/fixture/plugin-meta-worker.ts +26 -0
  572. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  573. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  574. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  575. package/test/fixture/skills/index.json +6 -0
  576. package/test/fixture/tui-plugin.ts +328 -0
  577. package/test/fixture/tui-runtime.ts +27 -0
  578. package/test/format/format.test.ts +171 -0
  579. package/test/git/git.test.ts +128 -0
  580. package/test/ide/ide.test.ts +82 -0
  581. package/test/installation/installation.test.ts +152 -0
  582. package/test/keybind.test.ts +421 -0
  583. package/test/lib/effect.ts +53 -0
  584. package/test/lib/filesystem.ts +10 -0
  585. package/test/lib/llm-server.ts +794 -0
  586. package/test/lsp/client.test.ts +95 -0
  587. package/test/lsp/index.test.ts +133 -0
  588. package/test/lsp/launch.test.ts +22 -0
  589. package/test/lsp/lifecycle.test.ts +147 -0
  590. package/test/mcp/headers.test.ts +153 -0
  591. package/test/mcp/lifecycle.test.ts +750 -0
  592. package/test/mcp/oauth-auto-connect.test.ts +199 -0
  593. package/test/mcp/oauth-browser.test.ts +249 -0
  594. package/test/mcp/sc-approve-validator.test.ts +431 -0
  595. package/test/memory/abort-leak.test.ts +137 -0
  596. package/test/npm.test.ts +18 -0
  597. package/test/patch/patch.test.ts +348 -0
  598. package/test/permission/arity.test.ts +33 -0
  599. package/test/permission/next.test.ts +1123 -0
  600. package/test/permission-task.test.ts +323 -0
  601. package/test/plugin/auth-override.test.ts +74 -0
  602. package/test/plugin/codex.test.ts +123 -0
  603. package/test/plugin/github-copilot-models.test.ts +117 -0
  604. package/test/plugin/install-concurrency.test.ts +140 -0
  605. package/test/plugin/install.test.ts +570 -0
  606. package/test/plugin/loader-shared.test.ts +1136 -0
  607. package/test/plugin/meta.test.ts +137 -0
  608. package/test/plugin/shared.test.ts +88 -0
  609. package/test/plugin/trigger.test.ts +111 -0
  610. package/test/preload.ts +90 -0
  611. package/test/project/migrate-global.test.ts +140 -0
  612. package/test/project/project.test.ts +459 -0
  613. package/test/project/state.test.ts +115 -0
  614. package/test/project/vcs.test.ts +228 -0
  615. package/test/project/worktree-remove.test.ts +96 -0
  616. package/test/project/worktree.test.ts +173 -0
  617. package/test/provider/amazon-bedrock.test.ts +447 -0
  618. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  619. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  620. package/test/provider/error.test.ts +49 -0
  621. package/test/provider/gitlab-duo.test.ts +412 -0
  622. package/test/provider/provider.test.ts +2494 -0
  623. package/test/provider/transform.test.ts +2944 -0
  624. package/test/pty/pty-output-isolation.test.ts +141 -0
  625. package/test/pty/pty-session.test.ts +92 -0
  626. package/test/pty/pty-shell.test.ts +59 -0
  627. package/test/question/question.test.ts +453 -0
  628. package/test/server/global-session-list.test.ts +89 -0
  629. package/test/server/project-init-git.test.ts +121 -0
  630. package/test/server/session-actions.test.ts +83 -0
  631. package/test/server/session-list.test.ts +98 -0
  632. package/test/server/session-messages.test.ts +159 -0
  633. package/test/server/session-select.test.ts +84 -0
  634. package/test/session/compaction.test.ts +683 -0
  635. package/test/session/continuity-handover.test.ts +620 -0
  636. package/test/session/deterministic-handover.test.ts +328 -0
  637. package/test/session/doom-protection.test.ts +247 -0
  638. package/test/session/hard-reset.test.ts +179 -0
  639. package/test/session/instruction.test.ts +286 -0
  640. package/test/session/llm/monitor.test.ts +53 -0
  641. package/test/session/llm-sanitizer.test.ts +90 -0
  642. package/test/session/llm-zones-e2e.test.ts +61 -0
  643. package/test/session/llm.test.ts +1308 -0
  644. package/test/session/mcpx-normalization.test.ts +86 -0
  645. package/test/session/mcpx-syntax-recovery.test.ts +28 -0
  646. package/test/session/message-v2.test.ts +957 -0
  647. package/test/session/messages-pagination.test.ts +885 -0
  648. package/test/session/processor-effect.test.ts +805 -0
  649. package/test/session/prompt/builder.test.ts +71 -0
  650. package/test/session/prompt/engine-loop.test.ts +80 -0
  651. package/test/session/prompt/orchestrator.test.ts +108 -0
  652. package/test/session/prompt/resolver.test.ts +211 -0
  653. package/test/session/prompt/router.test.ts +84 -0
  654. package/test/session/prompt/state.test.ts +57 -0
  655. package/test/session/prompt-effect.test.ts +1241 -0
  656. package/test/session/prompt.test.ts +522 -0
  657. package/test/session/refactor-system-zones.test.ts +241 -0
  658. package/test/session/retry.test.ts +232 -0
  659. package/test/session/revert-compact.test.ts +621 -0
  660. package/test/session/sanitizer.test.ts +61 -0
  661. package/test/session/session.test.ts +142 -0
  662. package/test/session/snapshot-tool-race.test.ts +242 -0
  663. package/test/session/structured-output-integration.test.ts +233 -0
  664. package/test/session/structured-output.test.ts +391 -0
  665. package/test/session/system.test.ts +59 -0
  666. package/test/session/telemetry.test.ts +35 -0
  667. package/test/shell/shell.test.ts +73 -0
  668. package/test/skill/discovery.test.ts +116 -0
  669. package/test/skill/skill.test.ts +392 -0
  670. package/test/snapshot/snapshot.test.ts +1404 -0
  671. package/test/storage/db.test.ts +14 -0
  672. package/test/storage/json-migration.test.ts +791 -0
  673. package/test/storage/storage.test.ts +295 -0
  674. package/test/sync/index.test.ts +191 -0
  675. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  676. package/test/tool/apply_patch.test.ts +567 -0
  677. package/test/tool/bash.test.ts +1099 -0
  678. package/test/tool/edit.test.ts +681 -0
  679. package/test/tool/external-directory.test.ts +198 -0
  680. package/test/tool/fixtures/large-image.png +0 -0
  681. package/test/tool/fixtures/models-api.json +65179 -0
  682. package/test/tool/grep.test.ts +111 -0
  683. package/test/tool/question.test.ts +126 -0
  684. package/test/tool/read.test.ts +468 -0
  685. package/test/tool/registry.test.ts +126 -0
  686. package/test/tool/skill.test.ts +167 -0
  687. package/test/tool/task.test.ts +49 -0
  688. package/test/tool/tool-define.test.ts +101 -0
  689. package/test/tool/truncation.test.ts +161 -0
  690. package/test/tool/webfetch.test.ts +101 -0
  691. package/test/tool/write.test.ts +354 -0
  692. package/test/util/data-url.test.ts +14 -0
  693. package/test/util/effect-zod.test.ts +61 -0
  694. package/test/util/error.test.ts +38 -0
  695. package/test/util/filesystem.test.ts +656 -0
  696. package/test/util/flock.test.ts +383 -0
  697. package/test/util/format.test.ts +59 -0
  698. package/test/util/glob.test.ts +164 -0
  699. package/test/util/iife.test.ts +36 -0
  700. package/test/util/lazy.test.ts +50 -0
  701. package/test/util/lock.test.ts +72 -0
  702. package/test/util/log-parser.test.ts +61 -0
  703. package/test/util/module.test.ts +59 -0
  704. package/test/util/process.test.ts +128 -0
  705. package/test/util/telemetry-integration.test.ts +104 -0
  706. package/test/util/timeout.test.ts +21 -0
  707. package/test/util/which.test.ts +100 -0
  708. package/test/util/wildcard.test.ts +90 -0
  709. package/test-regex.js +50 -0
  710. package/tsconfig.json +23 -0
@@ -0,0 +1,734 @@
1
+ // the approaches in this edit tool are sourced from
2
+ // https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-23-25.ts
3
+ // https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/utils/editCorrector.ts
4
+ // https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-26-25.ts
5
+
6
+ import z from "zod"
7
+ import * as path from "path"
8
+ import { Tool } from "./tool"
9
+ import { LSP } from "../lsp"
10
+ import { createTwoFilesPatch, diffLines } from "diff"
11
+ import DESCRIPTION from "./edit.txt"
12
+ import { File } from "../file"
13
+ import { FileWatcher } from "../file/watcher"
14
+ import { Bus } from "../bus"
15
+ import { Format } from "../format"
16
+ import { FileTime } from "../file/time"
17
+ import { Filesystem } from "../util/filesystem"
18
+ import { Instance } from "../project/instance"
19
+ import { Snapshot } from "@/snapshot"
20
+ import { assertExternalDirectory } from "./external-directory"
21
+ import { generateText } from "@/util/ai-sdk"
22
+ import { Provider } from "../provider/provider"
23
+
24
+ const MAX_DIAGNOSTICS_PER_FILE = 20
25
+
26
+ async function analyzeEditFailure(content: string, oldString: string, newString: string): Promise<string | null> {
27
+ try {
28
+ const sideLanguage = await Provider.getLanguage(
29
+ await Provider.getModel("local-side" as any, "nemotron-3-nano-4b" as any),
30
+ )
31
+ if (!sideLanguage) return null
32
+
33
+ const prompt = `You are a pair programmer assisting an AI agent. The agent tried to edit a file using a search-and-replace tool, but its 'oldString' was not found.
34
+ Analyze the actual file contents against what the agent tried to replace, and return a CONCISE, 1-2 sentence explanation of why it failed so the agent can fix it.
35
+ DO NOT return the raw code. ONLY return the explanation (e.g. "The signature changed to include ctx", "The function was deleted").
36
+
37
+ Intended oldString:
38
+ ${oldString}
39
+
40
+ Intended newString:
41
+ ${newString}
42
+
43
+ Actual File Content:
44
+ ${content.substring(0, 8000)}`
45
+
46
+ const response = await generateText({
47
+ model: sideLanguage,
48
+ prompt,
49
+ })
50
+
51
+ return response.text
52
+ } catch (error) {
53
+ // Silently fallback if the side model is offline or fails
54
+ return null
55
+ }
56
+ }
57
+
58
+ function normalizeLineEndings(text: string): string {
59
+ return text.replaceAll("\r\n", "\n")
60
+ }
61
+
62
+ function detectLineEnding(text: string): "\n" | "\r\n" {
63
+ return text.includes("\r\n") ? "\r\n" : "\n"
64
+ }
65
+
66
+ function convertToLineEnding(text: string, ending: "\n" | "\r\n"): string {
67
+ if (ending === "\n") return text
68
+ return text.replaceAll("\n", "\r\n")
69
+ }
70
+
71
+ export const EditTool = Tool.define("edit", {
72
+ description: DESCRIPTION,
73
+ parameters: z.object({
74
+ filePath: z.string().describe("The absolute path to modify"),
75
+ oldString: z.string().describe("The text to replace"),
76
+ newString: z.string().describe("The text to replace it with"),
77
+ replaceAll: z.boolean().optional().describe("Replace all occurrences (default false)"),
78
+ }),
79
+ async execute(params, ctx) {
80
+ if (!params.filePath) {
81
+ throw new Error("filePath is required")
82
+ }
83
+
84
+ if (params.oldString === params.newString) {
85
+ throw new Error("No changes to apply: oldString and newString are identical.")
86
+ }
87
+
88
+ const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
89
+ await assertExternalDirectory(ctx, filePath)
90
+
91
+ let diff = ""
92
+ let contentOld = ""
93
+ let contentNew = ""
94
+ await FileTime.withLock(filePath, async () => {
95
+ if (params.oldString === "") {
96
+ const existed = await Filesystem.exists(filePath)
97
+ contentNew = params.newString
98
+ diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
99
+ await ctx.ask({
100
+ permission: "edit",
101
+ patterns: [path.relative(Instance.worktree, filePath)],
102
+ always: ["*"],
103
+ metadata: {
104
+ filepath: filePath,
105
+ diff,
106
+ },
107
+ })
108
+ await Filesystem.write(filePath, params.newString)
109
+ await Format.file(filePath)
110
+ Bus.publish(File.Event.Edited, { file: filePath })
111
+ await Bus.publish(FileWatcher.Event.Updated, {
112
+ file: filePath,
113
+ event: existed ? "change" : "add",
114
+ })
115
+ await FileTime.read(ctx.sessionID, filePath)
116
+ return
117
+ }
118
+
119
+ const stats = Filesystem.stat(filePath)
120
+ if (!stats) throw new Error(`File ${filePath} not found`)
121
+ if (stats.isDirectory()) throw new Error(`Path is a directory, not a file: ${filePath}`)
122
+ try {
123
+ await FileTime.assert(ctx.sessionID, filePath)
124
+ } catch (error: any) {
125
+ if (error.message.includes("You must read file")) {
126
+ const content = await Filesystem.readText(filePath)
127
+ await FileTime.read(ctx.sessionID, filePath)
128
+ const maxLines = 2000
129
+ const lines = content.split("\n")
130
+ const truncated = lines.length > maxLines
131
+ const displayContent = lines.slice(0, maxLines).join("\n")
132
+ throw new Error(
133
+ `ERROR: Stale Write Protection. You cannot overwrite a file you have not read.\n\n[SYSTEM AUTO-READ]: To save you a turn, the system has automatically loaded the file content for you:\n\n--- CONTENT OF ${filePath} ---\n${displayContent}\n${truncated ? `\n... (truncated to ${maxLines} lines)` : ""}\n--------------------------------\n\nPlease review the contents above and issue your write/replace command again.`,
134
+ )
135
+ }
136
+ throw error
137
+ }
138
+ contentOld = await Filesystem.readText(filePath)
139
+
140
+ const ending = detectLineEnding(contentOld)
141
+ const old = convertToLineEnding(normalizeLineEndings(params.oldString), ending)
142
+ const next = convertToLineEnding(normalizeLineEndings(params.newString), ending)
143
+
144
+ try {
145
+ contentNew = replace(contentOld, old, next, params.replaceAll)
146
+ } catch (error: any) {
147
+ if (error.message.includes("Could not find oldString")) {
148
+ const helperMsg = await analyzeEditFailure(contentOld, old, next)
149
+ if (helperMsg) {
150
+ throw new Error(`${error.message}\n\n[Clerk Pair Programmer Analysis]:\n${helperMsg}`)
151
+ }
152
+ }
153
+ throw error
154
+ }
155
+
156
+ diff = trimDiff(
157
+ createTwoFilesPatch(filePath, filePath, normalizeLineEndings(contentOld), normalizeLineEndings(contentNew)),
158
+ )
159
+ await ctx.ask({
160
+ permission: "edit",
161
+ patterns: [path.relative(Instance.worktree, filePath)],
162
+ always: ["*"],
163
+ metadata: {
164
+ filepath: filePath,
165
+ diff,
166
+ },
167
+ })
168
+
169
+ // Create a safety net backup file
170
+ await Filesystem.write(filePath + ".bak", contentOld)
171
+
172
+ await Filesystem.write(filePath, contentNew)
173
+ await Format.file(filePath)
174
+ Bus.publish(File.Event.Edited, { file: filePath })
175
+ await Bus.publish(FileWatcher.Event.Updated, {
176
+ file: filePath,
177
+ event: "change",
178
+ })
179
+ contentNew = await Filesystem.readText(filePath)
180
+ diff = trimDiff(
181
+ createTwoFilesPatch(filePath, filePath, normalizeLineEndings(contentOld), normalizeLineEndings(contentNew)),
182
+ )
183
+ await FileTime.read(ctx.sessionID, filePath)
184
+ })
185
+
186
+ const filediff: Snapshot.FileDiff = {
187
+ file: filePath,
188
+ before: contentOld,
189
+ after: contentNew,
190
+ additions: 0,
191
+ deletions: 0,
192
+ }
193
+ for (const change of diffLines(contentOld, contentNew)) {
194
+ if (change.added) filediff.additions += change.count || 0
195
+ if (change.removed) filediff.deletions += change.count || 0
196
+ }
197
+
198
+ ctx.metadata({
199
+ metadata: {
200
+ diff,
201
+ filediff,
202
+ diagnostics: {},
203
+ },
204
+ })
205
+
206
+ let output = "Edit applied successfully."
207
+ await LSP.touchFile(filePath, true)
208
+ const diagnostics = await LSP.diagnostics()
209
+ const normalizedFilePath = Filesystem.normalizePath(filePath)
210
+ const issues = diagnostics[normalizedFilePath] ?? []
211
+ const errors = issues.filter((item) => item.severity === 1)
212
+ if (errors.length > 0) {
213
+ const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
214
+ const suffix =
215
+ errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
216
+ output += `\n\nLSP errors detected in this file, please fix:\n<diagnostics file="${filePath}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
217
+ }
218
+
219
+ return {
220
+ metadata: {
221
+ diagnostics,
222
+ diff,
223
+ filediff,
224
+ },
225
+ title: `${path.relative(Instance.worktree, filePath)}`,
226
+ output,
227
+ }
228
+ },
229
+ })
230
+
231
+ export type Replacer = (content: string, find: string) => Generator<string, void, unknown>
232
+
233
+ // Similarity thresholds for block anchor fallback matching
234
+ const SINGLE_CANDIDATE_SIMILARITY_THRESHOLD = 0.0
235
+ const MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD = 0.3
236
+
237
+ /**
238
+ * Levenshtein distance algorithm implementation
239
+ */
240
+ function levenshtein(a: string, b: string): number {
241
+ // Handle empty strings
242
+ if (a === "" || b === "") {
243
+ return Math.max(a.length, b.length)
244
+ }
245
+ const matrix = Array.from({ length: a.length + 1 }, (_, i) =>
246
+ Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)),
247
+ )
248
+
249
+ for (let i = 1; i <= a.length; i++) {
250
+ for (let j = 1; j <= b.length; j++) {
251
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1
252
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost)
253
+ }
254
+ }
255
+ return matrix[a.length][b.length]
256
+ }
257
+
258
+ export const SimpleReplacer: Replacer = function* (_content, find) {
259
+ yield find
260
+ }
261
+
262
+ export const LineTrimmedReplacer: Replacer = function* (content, find) {
263
+ const originalLines = content.split("\n")
264
+ const searchLines = find.split("\n")
265
+
266
+ if (searchLines[searchLines.length - 1] === "") {
267
+ searchLines.pop()
268
+ }
269
+
270
+ for (let i = 0; i <= originalLines.length - searchLines.length; i++) {
271
+ let matches = true
272
+
273
+ for (let j = 0; j < searchLines.length; j++) {
274
+ const originalTrimmed = originalLines[i + j].trim()
275
+ const searchTrimmed = searchLines[j].trim()
276
+
277
+ if (originalTrimmed !== searchTrimmed) {
278
+ matches = false
279
+ break
280
+ }
281
+ }
282
+
283
+ if (matches) {
284
+ let matchStartIndex = 0
285
+ for (let k = 0; k < i; k++) {
286
+ matchStartIndex += originalLines[k].length + 1
287
+ }
288
+
289
+ let matchEndIndex = matchStartIndex
290
+ for (let k = 0; k < searchLines.length; k++) {
291
+ matchEndIndex += originalLines[i + k].length
292
+ if (k < searchLines.length - 1) {
293
+ matchEndIndex += 1 // Add newline character except for the last line
294
+ }
295
+ }
296
+
297
+ yield content.substring(matchStartIndex, matchEndIndex)
298
+ }
299
+ }
300
+ }
301
+
302
+ export const BlockAnchorReplacer: Replacer = function* (content, find) {
303
+ const originalLines = content.split("\n")
304
+ const searchLines = find.split("\n")
305
+
306
+ if (searchLines.length < 3) {
307
+ return
308
+ }
309
+
310
+ if (searchLines[searchLines.length - 1] === "") {
311
+ searchLines.pop()
312
+ }
313
+
314
+ const firstLineSearch = searchLines[0].trim()
315
+ const lastLineSearch = searchLines[searchLines.length - 1].trim()
316
+ const searchBlockSize = searchLines.length
317
+
318
+ // Collect all candidate positions where both anchors match
319
+ const candidates: Array<{ startLine: number; endLine: number }> = []
320
+ for (let i = 0; i < originalLines.length; i++) {
321
+ if (originalLines[i].trim() !== firstLineSearch) {
322
+ continue
323
+ }
324
+
325
+ // Look for the matching last line after this first line
326
+ for (let j = i + 2; j < originalLines.length; j++) {
327
+ if (originalLines[j].trim() === lastLineSearch) {
328
+ candidates.push({ startLine: i, endLine: j })
329
+ break // Only match the first occurrence of the last line
330
+ }
331
+ }
332
+ }
333
+
334
+ // Return immediately if no candidates
335
+ if (candidates.length === 0) {
336
+ return
337
+ }
338
+
339
+ // Handle single candidate scenario (using relaxed threshold)
340
+ if (candidates.length === 1) {
341
+ const { startLine, endLine } = candidates[0]
342
+ const actualBlockSize = endLine - startLine + 1
343
+
344
+ let similarity = 0
345
+ let linesToCheck = Math.min(searchBlockSize - 2, actualBlockSize - 2) // Middle lines only
346
+
347
+ if (linesToCheck > 0) {
348
+ for (let j = 1; j < searchBlockSize - 1 && j < actualBlockSize - 1; j++) {
349
+ const originalLine = originalLines[startLine + j].trim()
350
+ const searchLine = searchLines[j].trim()
351
+ const maxLen = Math.max(originalLine.length, searchLine.length)
352
+ if (maxLen === 0) {
353
+ continue
354
+ }
355
+ const distance = levenshtein(originalLine, searchLine)
356
+ similarity += (1 - distance / maxLen) / linesToCheck
357
+
358
+ // Exit early when threshold is reached
359
+ if (similarity >= SINGLE_CANDIDATE_SIMILARITY_THRESHOLD) {
360
+ break
361
+ }
362
+ }
363
+ } else {
364
+ // No middle lines to compare, just accept based on anchors
365
+ similarity = 1.0
366
+ }
367
+
368
+ if (similarity >= SINGLE_CANDIDATE_SIMILARITY_THRESHOLD) {
369
+ let matchStartIndex = 0
370
+ for (let k = 0; k < startLine; k++) {
371
+ matchStartIndex += originalLines[k].length + 1
372
+ }
373
+ let matchEndIndex = matchStartIndex
374
+ for (let k = startLine; k <= endLine; k++) {
375
+ matchEndIndex += originalLines[k].length
376
+ if (k < endLine) {
377
+ matchEndIndex += 1 // Add newline character except for the last line
378
+ }
379
+ }
380
+ yield content.substring(matchStartIndex, matchEndIndex)
381
+ }
382
+ return
383
+ }
384
+
385
+ // Calculate similarity for multiple candidates
386
+ let bestMatch: { startLine: number; endLine: number } | null = null
387
+ let maxSimilarity = -1
388
+
389
+ for (const candidate of candidates) {
390
+ const { startLine, endLine } = candidate
391
+ const actualBlockSize = endLine - startLine + 1
392
+
393
+ let similarity = 0
394
+ let linesToCheck = Math.min(searchBlockSize - 2, actualBlockSize - 2) // Middle lines only
395
+
396
+ if (linesToCheck > 0) {
397
+ for (let j = 1; j < searchBlockSize - 1 && j < actualBlockSize - 1; j++) {
398
+ const originalLine = originalLines[startLine + j].trim()
399
+ const searchLine = searchLines[j].trim()
400
+ const maxLen = Math.max(originalLine.length, searchLine.length)
401
+ if (maxLen === 0) {
402
+ continue
403
+ }
404
+ const distance = levenshtein(originalLine, searchLine)
405
+ similarity += 1 - distance / maxLen
406
+ }
407
+ similarity /= linesToCheck // Average similarity
408
+ } else {
409
+ // No middle lines to compare, just accept based on anchors
410
+ similarity = 1.0
411
+ }
412
+
413
+ if (similarity > maxSimilarity) {
414
+ maxSimilarity = similarity
415
+ bestMatch = candidate
416
+ }
417
+ }
418
+
419
+ // Threshold judgment
420
+ if (maxSimilarity >= MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD && bestMatch) {
421
+ const { startLine, endLine } = bestMatch
422
+ let matchStartIndex = 0
423
+ for (let k = 0; k < startLine; k++) {
424
+ matchStartIndex += originalLines[k].length + 1
425
+ }
426
+ let matchEndIndex = matchStartIndex
427
+ for (let k = startLine; k <= endLine; k++) {
428
+ matchEndIndex += originalLines[k].length
429
+ if (k < endLine) {
430
+ matchEndIndex += 1
431
+ }
432
+ }
433
+ yield content.substring(matchStartIndex, matchEndIndex)
434
+ }
435
+ }
436
+
437
+ export const WhitespaceNormalizedReplacer: Replacer = function* (content, find) {
438
+ const normalizeWhitespace = (text: string) => text.replace(/\s+/g, " ").trim()
439
+ const normalizedFind = normalizeWhitespace(find)
440
+
441
+ // Handle single line matches
442
+ const lines = content.split("\n")
443
+ for (let i = 0; i < lines.length; i++) {
444
+ const line = lines[i]
445
+ if (normalizeWhitespace(line) === normalizedFind) {
446
+ yield line
447
+ } else {
448
+ // Only check for substring matches if the full line doesn't match
449
+ const normalizedLine = normalizeWhitespace(line)
450
+ if (normalizedLine.includes(normalizedFind)) {
451
+ // Find the actual substring in the original line that matches
452
+ const words = find.trim().split(/\s+/)
453
+ if (words.length > 0) {
454
+ const pattern = words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+")
455
+ try {
456
+ const regex = new RegExp(pattern)
457
+ const match = line.match(regex)
458
+ if (match) {
459
+ yield match[0]
460
+ }
461
+ } catch (e) {
462
+ // Invalid regex pattern, skip
463
+ }
464
+ }
465
+ }
466
+ }
467
+ }
468
+
469
+ // Handle multi-line matches
470
+ const findLines = find.split("\n")
471
+ if (findLines.length > 1) {
472
+ for (let i = 0; i <= lines.length - findLines.length; i++) {
473
+ const block = lines.slice(i, i + findLines.length)
474
+ if (normalizeWhitespace(block.join("\n")) === normalizedFind) {
475
+ yield block.join("\n")
476
+ }
477
+ }
478
+ }
479
+ }
480
+
481
+ export const IndentationFlexibleReplacer: Replacer = function* (content, find) {
482
+ const removeIndentation = (text: string) => {
483
+ const lines = text.split("\n")
484
+ const nonEmptyLines = lines.filter((line) => line.trim().length > 0)
485
+ if (nonEmptyLines.length === 0) return text
486
+
487
+ const minIndent = Math.min(
488
+ ...nonEmptyLines.map((line) => {
489
+ const match = line.match(/^(\s*)/)
490
+ return match ? match[1].length : 0
491
+ }),
492
+ )
493
+
494
+ return lines.map((line) => (line.trim().length === 0 ? line : line.slice(minIndent))).join("\n")
495
+ }
496
+
497
+ const normalizedFind = removeIndentation(find)
498
+ const contentLines = content.split("\n")
499
+ const findLines = find.split("\n")
500
+
501
+ for (let i = 0; i <= contentLines.length - findLines.length; i++) {
502
+ const block = contentLines.slice(i, i + findLines.length).join("\n")
503
+ if (removeIndentation(block) === normalizedFind) {
504
+ yield block
505
+ }
506
+ }
507
+ }
508
+
509
+ export const EscapeNormalizedReplacer: Replacer = function* (content, find) {
510
+ const unescapeString = (str: string): string => {
511
+ return str.replace(/\\(n|t|r|'|"|`|\\|\n|\$)/g, (match, capturedChar) => {
512
+ switch (capturedChar) {
513
+ case "n":
514
+ return "\n"
515
+ case "t":
516
+ return "\t"
517
+ case "r":
518
+ return "\r"
519
+ case "'":
520
+ return "'"
521
+ case '"':
522
+ return '"'
523
+ case "`":
524
+ return "`"
525
+ case "\\":
526
+ return "\\"
527
+ case "\n":
528
+ return "\n"
529
+ case "$":
530
+ return "$"
531
+ default:
532
+ return match
533
+ }
534
+ })
535
+ }
536
+
537
+ const unescapedFind = unescapeString(find)
538
+
539
+ // Try direct match with unescaped find string
540
+ if (content.includes(unescapedFind)) {
541
+ yield unescapedFind
542
+ }
543
+
544
+ // Also try finding escaped versions in content that match unescaped find
545
+ const lines = content.split("\n")
546
+ const findLines = unescapedFind.split("\n")
547
+
548
+ for (let i = 0; i <= lines.length - findLines.length; i++) {
549
+ const block = lines.slice(i, i + findLines.length).join("\n")
550
+ const unescapedBlock = unescapeString(block)
551
+
552
+ if (unescapedBlock === unescapedFind) {
553
+ yield block
554
+ }
555
+ }
556
+ }
557
+
558
+ export const MultiOccurrenceReplacer: Replacer = function* (content, find) {
559
+ // This replacer yields all exact matches, allowing the replace function
560
+ // to handle multiple occurrences based on replaceAll parameter
561
+ let startIndex = 0
562
+
563
+ while (true) {
564
+ const index = content.indexOf(find, startIndex)
565
+ if (index === -1) break
566
+
567
+ yield find
568
+ startIndex = index + find.length
569
+ }
570
+ }
571
+
572
+ export const TrimmedBoundaryReplacer: Replacer = function* (content, find) {
573
+ const trimmedFind = find.trim()
574
+
575
+ if (trimmedFind === find) {
576
+ // Already trimmed, no point in trying
577
+ return
578
+ }
579
+
580
+ // Try to find the trimmed version
581
+ if (content.includes(trimmedFind)) {
582
+ yield trimmedFind
583
+ }
584
+
585
+ // Also try finding blocks where trimmed content matches
586
+ const lines = content.split("\n")
587
+ const findLines = find.split("\n")
588
+
589
+ for (let i = 0; i <= lines.length - findLines.length; i++) {
590
+ const block = lines.slice(i, i + findLines.length).join("\n")
591
+
592
+ if (block.trim() === trimmedFind) {
593
+ yield block
594
+ }
595
+ }
596
+ }
597
+
598
+ export const ContextAwareReplacer: Replacer = function* (content, find) {
599
+ const findLines = find.split("\n")
600
+ if (findLines.length < 3) {
601
+ // Need at least 3 lines to have meaningful context
602
+ return
603
+ }
604
+
605
+ // Remove trailing empty line if present
606
+ if (findLines[findLines.length - 1] === "") {
607
+ findLines.pop()
608
+ }
609
+
610
+ const contentLines = content.split("\n")
611
+
612
+ // Extract first and last lines as context anchors
613
+ const firstLine = findLines[0].trim()
614
+ const lastLine = findLines[findLines.length - 1].trim()
615
+
616
+ // Find blocks that start and end with the context anchors
617
+ for (let i = 0; i < contentLines.length; i++) {
618
+ if (contentLines[i].trim() !== firstLine) continue
619
+
620
+ // Look for the matching last line
621
+ for (let j = i + 2; j < contentLines.length; j++) {
622
+ if (contentLines[j].trim() === lastLine) {
623
+ // Found a potential context block
624
+ const blockLines = contentLines.slice(i, j + 1)
625
+ const block = blockLines.join("\n")
626
+
627
+ // Check if the middle content has reasonable similarity
628
+ // (simple heuristic: at least 50% of non-empty lines should match when trimmed)
629
+ if (blockLines.length === findLines.length) {
630
+ let matchingLines = 0
631
+ let totalNonEmptyLines = 0
632
+
633
+ for (let k = 1; k < blockLines.length - 1; k++) {
634
+ const blockLine = blockLines[k].trim()
635
+ const findLine = findLines[k].trim()
636
+
637
+ if (blockLine.length > 0 || findLine.length > 0) {
638
+ totalNonEmptyLines++
639
+ if (blockLine === findLine) {
640
+ matchingLines++
641
+ }
642
+ }
643
+ }
644
+
645
+ if (totalNonEmptyLines === 0 || matchingLines / totalNonEmptyLines >= 0.5) {
646
+ yield block
647
+ break // Only match the first occurrence
648
+ }
649
+ }
650
+ break
651
+ }
652
+ }
653
+ }
654
+ }
655
+
656
+ export function trimDiff(diff: string): string {
657
+ const lines = diff.split("\n")
658
+ const contentLines = lines.filter(
659
+ (line) =>
660
+ (line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) &&
661
+ !line.startsWith("---") &&
662
+ !line.startsWith("+++"),
663
+ )
664
+
665
+ if (contentLines.length === 0) return diff
666
+
667
+ let min = Infinity
668
+ for (const line of contentLines) {
669
+ const content = line.slice(1)
670
+ if (content.trim().length > 0) {
671
+ const match = content.match(/^(\s*)/)
672
+ if (match) min = Math.min(min, match[1].length)
673
+ }
674
+ }
675
+ if (min === Infinity || min === 0) return diff
676
+ const trimmedLines = lines.map((line) => {
677
+ if (
678
+ (line.startsWith("+") || line.startsWith("-") || line.startsWith(" ")) &&
679
+ !line.startsWith("---") &&
680
+ !line.startsWith("+++")
681
+ ) {
682
+ const prefix = line[0]
683
+ const content = line.slice(1)
684
+ return prefix + content.slice(min)
685
+ }
686
+ return line
687
+ })
688
+
689
+ return trimmedLines.join("\n")
690
+ }
691
+
692
+ export function replace(content: string, oldString: string, newString: string, replaceAll = false): string {
693
+ if (oldString === newString) {
694
+ throw new Error("No changes to apply: oldString and newString are identical.")
695
+ }
696
+
697
+ let notFound = true
698
+
699
+ for (const replacer of [
700
+ SimpleReplacer,
701
+ LineTrimmedReplacer,
702
+ BlockAnchorReplacer,
703
+ WhitespaceNormalizedReplacer,
704
+ IndentationFlexibleReplacer,
705
+ EscapeNormalizedReplacer,
706
+ TrimmedBoundaryReplacer,
707
+ ContextAwareReplacer,
708
+ MultiOccurrenceReplacer,
709
+ ]) {
710
+ for (const search of replacer(content, oldString)) {
711
+ const index = content.indexOf(search)
712
+ if (index === -1) continue
713
+ notFound = false
714
+ if (replaceAll) {
715
+ return content.replaceAll(search, newString)
716
+ }
717
+ const lastIndex = content.lastIndexOf(search)
718
+ if (index !== lastIndex) continue
719
+ return content.substring(0, index) + newString + content.substring(index + search.length)
720
+ }
721
+ }
722
+
723
+ if (notFound) {
724
+ const preview = content.split("\\n").slice(0, 100).join("\\n")
725
+ const previewMessage =
726
+ content.split("\\n").length > 100
727
+ ? `\\n\\nFirst 100 lines of current file:\\n${preview}\\n...`
728
+ : `\\n\\nCurrent file contents:\\n${preview}`
729
+ throw new Error(
730
+ `Could not find oldString in the file. It must match exactly, including whitespace, indentation, and line endings.${previewMessage}`,
731
+ )
732
+ }
733
+ throw new Error("Found multiple matches for oldString. Provide more surrounding context to make the match unique.")
734
+ }