@toolkit-cli/toolkode 1.3.7

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 (676) hide show
  1. package/AGENTS.md +69 -0
  2. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +15 -0
  5. package/bin/opencode +179 -0
  6. package/bin/toolkode +17 -0
  7. package/bin/toolkode.cjs +190 -0
  8. package/bunfig.toml +7 -0
  9. package/drizzle.config.ts +10 -0
  10. package/git +0 -0
  11. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  12. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  13. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  14. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  15. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  16. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  17. package/migration/20260225215848_workspace/migration.sql +7 -0
  18. package/migration/20260225215848_workspace/snapshot.json +959 -0
  19. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  20. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  21. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  22. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  23. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  24. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  25. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  26. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  27. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  28. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  29. package/migration/20260323234822_events/migration.sql +13 -0
  30. package/migration/20260323234822_events/snapshot.json +1271 -0
  31. package/package.json +160 -0
  32. package/parsers-config.ts +290 -0
  33. package/script/build-node.ts +54 -0
  34. package/script/build.ts +276 -0
  35. package/script/check-migrations.ts +16 -0
  36. package/script/postinstall.mjs +131 -0
  37. package/script/publish.ts +181 -0
  38. package/script/schema.ts +63 -0
  39. package/script/seed-e2e.ts +60 -0
  40. package/script/upgrade-opentui.ts +64 -0
  41. package/specs/effect-migration.md +293 -0
  42. package/specs/tui-plugins.md +389 -0
  43. package/src/account/account.sql.ts +39 -0
  44. package/src/account/index.ts +397 -0
  45. package/src/account/repo.ts +163 -0
  46. package/src/account/schema.ts +91 -0
  47. package/src/acp/README.md +174 -0
  48. package/src/acp/agent.ts +1743 -0
  49. package/src/acp/session.ts +116 -0
  50. package/src/acp/types.ts +24 -0
  51. package/src/agent/agent.ts +418 -0
  52. package/src/agent/generate.txt +75 -0
  53. package/src/agent/prompt/compaction.txt +14 -0
  54. package/src/agent/prompt/explore.txt +18 -0
  55. package/src/agent/prompt/summary.txt +11 -0
  56. package/src/agent/prompt/title.txt +44 -0
  57. package/src/auth/index.ts +115 -0
  58. package/src/bun/index.ts +128 -0
  59. package/src/bun/registry.ts +50 -0
  60. package/src/bus/bus-event.ts +40 -0
  61. package/src/bus/global.ts +10 -0
  62. package/src/bus/index.ts +184 -0
  63. package/src/channel/index.ts +231 -0
  64. package/src/cli/bootstrap.ts +17 -0
  65. package/src/cli/cmd/account.ts +257 -0
  66. package/src/cli/cmd/acp.ts +70 -0
  67. package/src/cli/cmd/agent.ts +245 -0
  68. package/src/cli/cmd/cmd.ts +7 -0
  69. package/src/cli/cmd/db.ts +119 -0
  70. package/src/cli/cmd/debug/agent.ts +167 -0
  71. package/src/cli/cmd/debug/config.ts +16 -0
  72. package/src/cli/cmd/debug/file.ts +97 -0
  73. package/src/cli/cmd/debug/index.ts +48 -0
  74. package/src/cli/cmd/debug/lsp.ts +53 -0
  75. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  76. package/src/cli/cmd/debug/scrap.ts +16 -0
  77. package/src/cli/cmd/debug/skill.ts +16 -0
  78. package/src/cli/cmd/debug/snapshot.ts +52 -0
  79. package/src/cli/cmd/export.ts +89 -0
  80. package/src/cli/cmd/generate.ts +38 -0
  81. package/src/cli/cmd/github.ts +1646 -0
  82. package/src/cli/cmd/import.ts +207 -0
  83. package/src/cli/cmd/mcp.ts +754 -0
  84. package/src/cli/cmd/models.ts +78 -0
  85. package/src/cli/cmd/plug.ts +231 -0
  86. package/src/cli/cmd/pr.ts +127 -0
  87. package/src/cli/cmd/providers.ts +482 -0
  88. package/src/cli/cmd/run.ts +738 -0
  89. package/src/cli/cmd/serve.ts +42 -0
  90. package/src/cli/cmd/session.ts +159 -0
  91. package/src/cli/cmd/stats.ts +410 -0
  92. package/src/cli/cmd/tui/app.tsx +1255 -0
  93. package/src/cli/cmd/tui/attach.ts +88 -0
  94. package/src/cli/cmd/tui/component/border.tsx +21 -0
  95. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  96. package/src/cli/cmd/tui/component/dialog-command.tsx +171 -0
  97. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  98. package/src/cli/cmd/tui/component/dialog-model.tsx +264 -0
  99. package/src/cli/cmd/tui/component/dialog-provider.tsx +334 -0
  100. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  101. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  102. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  103. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  104. package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
  105. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  106. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  107. package/src/cli/cmd/tui/component/dialog-variant.tsx +29 -0
  108. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +320 -0
  109. package/src/cli/cmd/tui/component/error-component.tsx +91 -0
  110. package/src/cli/cmd/tui/component/logo.tsx +86 -0
  111. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  112. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +667 -0
  113. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  114. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  115. package/src/cli/cmd/tui/component/prompt/index.tsx +1353 -0
  116. package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
  117. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  118. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  119. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  120. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  121. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  122. package/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx +151 -0
  123. package/src/cli/cmd/tui/context/args.tsx +15 -0
  124. package/src/cli/cmd/tui/context/directory.ts +13 -0
  125. package/src/cli/cmd/tui/context/exit.tsx +60 -0
  126. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  127. package/src/cli/cmd/tui/context/keybind.tsx +105 -0
  128. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  129. package/src/cli/cmd/tui/context/local.tsx +406 -0
  130. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  131. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  132. package/src/cli/cmd/tui/context/route.tsx +52 -0
  133. package/src/cli/cmd/tui/context/sdk.tsx +128 -0
  134. package/src/cli/cmd/tui/context/sync.tsx +504 -0
  135. package/src/cli/cmd/tui/context/theme/amber.json +245 -0
  136. package/src/cli/cmd/tui/context/theme/amiga.json +245 -0
  137. package/src/cli/cmd/tui/context/theme/atari.json +245 -0
  138. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  139. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  140. package/src/cli/cmd/tui/context/theme/borland.json +245 -0
  141. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  142. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  143. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  144. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  145. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  146. package/src/cli/cmd/tui/context/theme/commodore.json +245 -0
  147. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  148. package/src/cli/cmd/tui/context/theme/dos-edit.json +245 -0
  149. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  150. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  151. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  152. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  153. package/src/cli/cmd/tui/context/theme/gnu.json +245 -0
  154. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  155. package/src/cli/cmd/tui/context/theme/hacker.json +245 -0
  156. package/src/cli/cmd/tui/context/theme/irix.json +245 -0
  157. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  158. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  159. package/src/cli/cmd/tui/context/theme/mac84.json +245 -0
  160. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  161. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  162. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  163. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  164. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  165. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  166. package/src/cli/cmd/tui/context/theme/norton.json +245 -0
  167. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  168. package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
  169. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  170. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  171. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  172. package/src/cli/cmd/tui/context/theme/pine.json +245 -0
  173. package/src/cli/cmd/tui/context/theme/retrowave.json +245 -0
  174. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  175. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  176. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  177. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  178. package/src/cli/cmd/tui/context/theme/toolkode.json +245 -0
  179. package/src/cli/cmd/tui/context/theme/tron.json +245 -0
  180. package/src/cli/cmd/tui/context/theme/ubuntu.json +245 -0
  181. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  182. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  183. package/src/cli/cmd/tui/context/theme/vt100.json +245 -0
  184. package/src/cli/cmd/tui/context/theme/xcode.json +245 -0
  185. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  186. package/src/cli/cmd/tui/context/theme.tsx +1288 -0
  187. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  188. package/src/cli/cmd/tui/event.ts +49 -0
  189. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +152 -0
  190. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
  191. package/src/cli/cmd/tui/feature-plugins/sidebar/agents-panel.tsx +95 -0
  192. package/src/cli/cmd/tui/feature-plugins/sidebar/btw-panel.tsx +105 -0
  193. package/src/cli/cmd/tui/feature-plugins/sidebar/commands-panel.tsx +40 -0
  194. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
  195. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  196. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
  197. package/src/cli/cmd/tui/feature-plugins/sidebar/git-panel.tsx +36 -0
  198. package/src/cli/cmd/tui/feature-plugins/sidebar/loop-panel.tsx +124 -0
  199. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  200. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
  201. package/src/cli/cmd/tui/feature-plugins/sidebar/session-panel.tsx +48 -0
  202. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
  203. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +270 -0
  204. package/src/cli/cmd/tui/plugin/api.tsx +420 -0
  205. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  206. package/src/cli/cmd/tui/plugin/internal.ts +37 -0
  207. package/src/cli/cmd/tui/plugin/runtime.ts +967 -0
  208. package/src/cli/cmd/tui/plugin/slots.tsx +61 -0
  209. package/src/cli/cmd/tui/routes/home.tsx +173 -0
  210. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
  211. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
  212. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  213. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  214. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  215. package/src/cli/cmd/tui/routes/session/index.tsx +2229 -0
  216. package/src/cli/cmd/tui/routes/session/permission.tsx +685 -0
  217. package/src/cli/cmd/tui/routes/session/question.tsx +467 -0
  218. package/src/cli/cmd/tui/routes/session/sidebar.tsx +72 -0
  219. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +131 -0
  220. package/src/cli/cmd/tui/thread.ts +232 -0
  221. package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
  222. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +89 -0
  223. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +208 -0
  224. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  225. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +106 -0
  226. package/src/cli/cmd/tui/ui/dialog-select.tsx +402 -0
  227. package/src/cli/cmd/tui/ui/dialog.tsx +192 -0
  228. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  229. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  230. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  231. package/src/cli/cmd/tui/util/clipboard.ts +192 -0
  232. package/src/cli/cmd/tui/util/editor.ts +37 -0
  233. package/src/cli/cmd/tui/util/selection.ts +25 -0
  234. package/src/cli/cmd/tui/util/signal.ts +7 -0
  235. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  236. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  237. package/src/cli/cmd/tui/win32.ts +129 -0
  238. package/src/cli/cmd/tui/worker.ts +204 -0
  239. package/src/cli/cmd/uninstall.ts +353 -0
  240. package/src/cli/cmd/upgrade.ts +73 -0
  241. package/src/cli/cmd/web.ts +81 -0
  242. package/src/cli/effect/prompt.ts +25 -0
  243. package/src/cli/error.ts +46 -0
  244. package/src/cli/logo.ts +7 -0
  245. package/src/cli/network.ts +60 -0
  246. package/src/cli/ui.ts +116 -0
  247. package/src/cli/upgrade.ts +31 -0
  248. package/src/command/index.ts +195 -0
  249. package/src/command/template/initialize.txt +10 -0
  250. package/src/command/template/review.txt +101 -0
  251. package/src/config/config.ts +1693 -0
  252. package/src/config/markdown.ts +99 -0
  253. package/src/config/migrate-tui-config.ts +155 -0
  254. package/src/config/paths.ts +174 -0
  255. package/src/config/tui-schema.ts +36 -0
  256. package/src/config/tui.ts +212 -0
  257. package/src/control-plane/adaptors/index.ts +20 -0
  258. package/src/control-plane/adaptors/worktree.ts +38 -0
  259. package/src/control-plane/schema.ts +17 -0
  260. package/src/control-plane/sse.ts +66 -0
  261. package/src/control-plane/types.ts +21 -0
  262. package/src/control-plane/workspace.sql.ts +17 -0
  263. package/src/control-plane/workspace.ts +154 -0
  264. package/src/cron/index.ts +241 -0
  265. package/src/cron/parse.ts +189 -0
  266. package/src/effect/cross-spawn-spawner.ts +479 -0
  267. package/src/effect/instance-registry.ts +12 -0
  268. package/src/effect/instance-state.ts +47 -0
  269. package/src/effect/run-service.ts +19 -0
  270. package/src/env/index.ts +28 -0
  271. package/src/file/ignore.ts +82 -0
  272. package/src/file/index.ts +693 -0
  273. package/src/file/protected.ts +59 -0
  274. package/src/file/ripgrep.ts +376 -0
  275. package/src/file/time.ts +128 -0
  276. package/src/file/watcher.ts +171 -0
  277. package/src/filesystem/index.ts +226 -0
  278. package/src/flag/flag.ts +157 -0
  279. package/src/format/formatter.ts +396 -0
  280. package/src/format/index.ts +199 -0
  281. package/src/global/index.ts +54 -0
  282. package/src/hooks/index.ts +302 -0
  283. package/src/id/id.ts +85 -0
  284. package/src/ide/index.ts +74 -0
  285. package/src/index.ts +243 -0
  286. package/src/installation/index.ts +363 -0
  287. package/src/lsp/client.ts +252 -0
  288. package/src/lsp/index.ts +558 -0
  289. package/src/lsp/language.ts +120 -0
  290. package/src/lsp/launch.ts +21 -0
  291. package/src/lsp/server.ts +2093 -0
  292. package/src/mcp/auth.ts +181 -0
  293. package/src/mcp/index.ts +926 -0
  294. package/src/mcp/oauth-callback.ts +215 -0
  295. package/src/mcp/oauth-provider.ts +185 -0
  296. package/src/node.ts +1 -0
  297. package/src/patch/index.ts +680 -0
  298. package/src/permission/arity.ts +163 -0
  299. package/src/permission/evaluate.ts +15 -0
  300. package/src/permission/index.ts +322 -0
  301. package/src/permission/schema.ts +17 -0
  302. package/src/plugin/codex.ts +628 -0
  303. package/src/plugin/copilot.ts +343 -0
  304. package/src/plugin/index.ts +331 -0
  305. package/src/plugin/install.ts +384 -0
  306. package/src/plugin/meta.ts +165 -0
  307. package/src/plugin/shared.ts +172 -0
  308. package/src/project/bootstrap.ts +31 -0
  309. package/src/project/instance.ts +167 -0
  310. package/src/project/project.sql.ts +16 -0
  311. package/src/project/project.ts +519 -0
  312. package/src/project/schema.ts +16 -0
  313. package/src/project/state.ts +70 -0
  314. package/src/project/vcs.ts +124 -0
  315. package/src/provider/auth.ts +252 -0
  316. package/src/provider/error.ts +197 -0
  317. package/src/provider/models.ts +138 -0
  318. package/src/provider/provider.ts +1593 -0
  319. package/src/provider/schema.ts +39 -0
  320. package/src/provider/sdk/copilot/README.md +5 -0
  321. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  322. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  323. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  324. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  325. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +815 -0
  326. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  327. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  328. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  329. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  330. package/src/provider/sdk/copilot/index.ts +2 -0
  331. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  332. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
  333. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  334. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  335. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  336. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
  337. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1769 -0
  338. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
  339. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  340. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
  341. package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
  342. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
  343. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
  344. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
  345. package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
  346. package/src/provider/toolkit-manifest.ts +110 -0
  347. package/src/provider/transform.ts +1045 -0
  348. package/src/pty/index.ts +397 -0
  349. package/src/pty/schema.ts +17 -0
  350. package/src/question/index.ts +221 -0
  351. package/src/question/schema.ts +17 -0
  352. package/src/server/error.ts +36 -0
  353. package/src/server/event.ts +7 -0
  354. package/src/server/instance.ts +285 -0
  355. package/src/server/mdns.ts +60 -0
  356. package/src/server/middleware.ts +29 -0
  357. package/src/server/projectors.ts +28 -0
  358. package/src/server/router.ts +99 -0
  359. package/src/server/routes/config.ts +92 -0
  360. package/src/server/routes/event.ts +83 -0
  361. package/src/server/routes/experimental.ts +271 -0
  362. package/src/server/routes/file.ts +197 -0
  363. package/src/server/routes/global.ts +339 -0
  364. package/src/server/routes/mcp.ts +225 -0
  365. package/src/server/routes/permission.ts +69 -0
  366. package/src/server/routes/project.ts +118 -0
  367. package/src/server/routes/provider.ts +171 -0
  368. package/src/server/routes/pty.ts +211 -0
  369. package/src/server/routes/question.ts +99 -0
  370. package/src/server/routes/session.ts +1031 -0
  371. package/src/server/routes/tui.ts +379 -0
  372. package/src/server/routes/workspace.ts +94 -0
  373. package/src/server/server.ts +312 -0
  374. package/src/session/compaction.ts +424 -0
  375. package/src/session/index.ts +882 -0
  376. package/src/session/instruction.ts +321 -0
  377. package/src/session/llm.ts +341 -0
  378. package/src/session/message-v2.ts +1030 -0
  379. package/src/session/message.ts +191 -0
  380. package/src/session/overflow.ts +22 -0
  381. package/src/session/processor.ts +554 -0
  382. package/src/session/projectors.ts +135 -0
  383. package/src/session/prompt/anthropic.txt +105 -0
  384. package/src/session/prompt/beast.txt +147 -0
  385. package/src/session/prompt/build-switch.txt +5 -0
  386. package/src/session/prompt/codex.txt +79 -0
  387. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  388. package/src/session/prompt/default.txt +108 -0
  389. package/src/session/prompt/gemini.txt +155 -0
  390. package/src/session/prompt/gpt.txt +107 -0
  391. package/src/session/prompt/max-steps.txt +16 -0
  392. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  393. package/src/session/prompt/plan.txt +26 -0
  394. package/src/session/prompt/trinity.txt +97 -0
  395. package/src/session/prompt.ts +2058 -0
  396. package/src/session/retry.ts +106 -0
  397. package/src/session/revert.ts +138 -0
  398. package/src/session/schema.ts +38 -0
  399. package/src/session/session.sql.ts +103 -0
  400. package/src/session/status.ts +102 -0
  401. package/src/session/summary.ts +170 -0
  402. package/src/session/system.ts +74 -0
  403. package/src/session/todo.ts +57 -0
  404. package/src/share/share-next.ts +288 -0
  405. package/src/share/share.sql.ts +13 -0
  406. package/src/shell/shell.ts +73 -0
  407. package/src/skill/discovery.ts +116 -0
  408. package/src/skill/index.ts +284 -0
  409. package/src/skills-marketplace/index.ts +305 -0
  410. package/src/snapshot/index.ts +489 -0
  411. package/src/sql.d.ts +4 -0
  412. package/src/storage/db.bun.ts +8 -0
  413. package/src/storage/db.node.ts +8 -0
  414. package/src/storage/db.ts +177 -0
  415. package/src/storage/json-migration.ts +425 -0
  416. package/src/storage/schema.sql.ts +10 -0
  417. package/src/storage/schema.ts +5 -0
  418. package/src/storage/storage.ts +217 -0
  419. package/src/sync/README.md +179 -0
  420. package/src/sync/event.sql.ts +16 -0
  421. package/src/sync/index.ts +263 -0
  422. package/src/sync/schema.ts +14 -0
  423. package/src/team/index.ts +428 -0
  424. package/src/tool/apply_patch.ts +281 -0
  425. package/src/tool/apply_patch.txt +33 -0
  426. package/src/tool/bash.ts +271 -0
  427. package/src/tool/bash.txt +115 -0
  428. package/src/tool/batch.ts +183 -0
  429. package/src/tool/batch.txt +24 -0
  430. package/src/tool/codesearch.ts +132 -0
  431. package/src/tool/codesearch.txt +12 -0
  432. package/src/tool/cron-create.ts +54 -0
  433. package/src/tool/cron-create.txt +16 -0
  434. package/src/tool/cron-delete.ts +29 -0
  435. package/src/tool/cron-delete.txt +1 -0
  436. package/src/tool/cron-list.ts +41 -0
  437. package/src/tool/cron-list.txt +1 -0
  438. package/src/tool/edit.ts +667 -0
  439. package/src/tool/edit.txt +10 -0
  440. package/src/tool/external-directory.ts +32 -0
  441. package/src/tool/glob.ts +78 -0
  442. package/src/tool/glob.txt +6 -0
  443. package/src/tool/grep.ts +156 -0
  444. package/src/tool/grep.txt +8 -0
  445. package/src/tool/invalid.ts +17 -0
  446. package/src/tool/ls.ts +121 -0
  447. package/src/tool/ls.txt +1 -0
  448. package/src/tool/lsp.ts +97 -0
  449. package/src/tool/lsp.txt +19 -0
  450. package/src/tool/multiedit.ts +46 -0
  451. package/src/tool/multiedit.txt +41 -0
  452. package/src/tool/plan-enter.txt +14 -0
  453. package/src/tool/plan-exit.txt +13 -0
  454. package/src/tool/plan.ts +131 -0
  455. package/src/tool/question.ts +33 -0
  456. package/src/tool/question.txt +10 -0
  457. package/src/tool/read.ts +293 -0
  458. package/src/tool/read.txt +14 -0
  459. package/src/tool/registry.ts +232 -0
  460. package/src/tool/schema.ts +17 -0
  461. package/src/tool/send-message.ts +59 -0
  462. package/src/tool/send-message.txt +7 -0
  463. package/src/tool/skill.ts +105 -0
  464. package/src/tool/task.ts +230 -0
  465. package/src/tool/task.txt +62 -0
  466. package/src/tool/team.ts +235 -0
  467. package/src/tool/team.txt +22 -0
  468. package/src/tool/todo.ts +31 -0
  469. package/src/tool/todowrite.txt +167 -0
  470. package/src/tool/tool.ts +90 -0
  471. package/src/tool/truncate.ts +144 -0
  472. package/src/tool/truncation-dir.ts +4 -0
  473. package/src/tool/webfetch.ts +206 -0
  474. package/src/tool/webfetch.txt +13 -0
  475. package/src/tool/websearch.ts +150 -0
  476. package/src/tool/websearch.txt +14 -0
  477. package/src/tool/write.ts +84 -0
  478. package/src/tool/write.txt +8 -0
  479. package/src/util/abort.ts +35 -0
  480. package/src/util/archive.ts +17 -0
  481. package/src/util/color.ts +19 -0
  482. package/src/util/context.ts +25 -0
  483. package/src/util/data-url.ts +9 -0
  484. package/src/util/defer.ts +12 -0
  485. package/src/util/effect-http-client.ts +11 -0
  486. package/src/util/effect-zod.ts +98 -0
  487. package/src/util/error.ts +77 -0
  488. package/src/util/filesystem.ts +203 -0
  489. package/src/util/flock.ts +333 -0
  490. package/src/util/fn.ts +21 -0
  491. package/src/util/format.ts +20 -0
  492. package/src/util/git.ts +35 -0
  493. package/src/util/glob.ts +34 -0
  494. package/src/util/hash.ts +7 -0
  495. package/src/util/iife.ts +3 -0
  496. package/src/util/keybind.ts +103 -0
  497. package/src/util/lazy.ts +23 -0
  498. package/src/util/locale.ts +81 -0
  499. package/src/util/lock.ts +98 -0
  500. package/src/util/log.ts +182 -0
  501. package/src/util/network.ts +9 -0
  502. package/src/util/process.ts +172 -0
  503. package/src/util/queue.ts +32 -0
  504. package/src/util/record.ts +3 -0
  505. package/src/util/rpc.ts +66 -0
  506. package/src/util/schema.ts +53 -0
  507. package/src/util/scrap.ts +10 -0
  508. package/src/util/signal.ts +12 -0
  509. package/src/util/timeout.ts +14 -0
  510. package/src/util/token.ts +7 -0
  511. package/src/util/update-schema.ts +13 -0
  512. package/src/util/which.ts +14 -0
  513. package/src/util/wildcard.ts +59 -0
  514. package/src/worktree/index.ts +638 -0
  515. package/sst-env.d.ts +10 -0
  516. package/test/AGENTS.md +81 -0
  517. package/test/account/repo.test.ts +326 -0
  518. package/test/account/service.test.ts +282 -0
  519. package/test/acp/agent-interface.test.ts +51 -0
  520. package/test/acp/event-subscription.test.ts +685 -0
  521. package/test/agent/agent.test.ts +717 -0
  522. package/test/auth/auth.test.ts +58 -0
  523. package/test/bun.test.ts +53 -0
  524. package/test/bus/bus-effect.test.ts +164 -0
  525. package/test/bus/bus-integration.test.ts +87 -0
  526. package/test/bus/bus.test.ts +219 -0
  527. package/test/cli/account.test.ts +26 -0
  528. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  529. package/test/cli/github-action.test.ts +198 -0
  530. package/test/cli/github-remote.test.ts +80 -0
  531. package/test/cli/import.test.ts +54 -0
  532. package/test/cli/plugin-auth-picker.test.ts +120 -0
  533. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  534. package/test/cli/tui/plugin-add.test.ts +61 -0
  535. package/test/cli/tui/plugin-install.test.ts +95 -0
  536. package/test/cli/tui/plugin-lifecycle.test.ts +225 -0
  537. package/test/cli/tui/plugin-loader-entrypoint.test.ts +189 -0
  538. package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
  539. package/test/cli/tui/plugin-loader.test.ts +563 -0
  540. package/test/cli/tui/plugin-toggle.test.ts +157 -0
  541. package/test/cli/tui/theme-store.test.ts +51 -0
  542. package/test/cli/tui/thread.test.ts +128 -0
  543. package/test/cli/tui/transcript.test.ts +322 -0
  544. package/test/config/agent-color.test.ts +71 -0
  545. package/test/config/config.test.ts +2187 -0
  546. package/test/config/fixtures/empty-frontmatter.md +4 -0
  547. package/test/config/fixtures/frontmatter.md +28 -0
  548. package/test/config/fixtures/markdown-header.md +11 -0
  549. package/test/config/fixtures/no-frontmatter.md +1 -0
  550. package/test/config/fixtures/weird-model-id.md +13 -0
  551. package/test/config/markdown.test.ts +228 -0
  552. package/test/config/tui.test.ts +667 -0
  553. package/test/control-plane/sse.test.ts +56 -0
  554. package/test/effect/cross-spawn-spawner.test.ts +402 -0
  555. package/test/effect/instance-state.test.ts +384 -0
  556. package/test/effect/run-service.test.ts +46 -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 +354 -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 +141 -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 +335 -0
  577. package/test/fixture/tui-runtime.ts +34 -0
  578. package/test/format/format.test.ts +179 -0
  579. package/test/ide/ide.test.ts +82 -0
  580. package/test/installation/installation.test.ts +151 -0
  581. package/test/keybind.test.ts +421 -0
  582. package/test/lib/effect.ts +37 -0
  583. package/test/lib/filesystem.ts +10 -0
  584. package/test/lsp/client.test.ts +95 -0
  585. package/test/lsp/index.test.ts +55 -0
  586. package/test/lsp/launch.test.ts +22 -0
  587. package/test/lsp/lifecycle.test.ts +147 -0
  588. package/test/mcp/headers.test.ts +153 -0
  589. package/test/mcp/lifecycle.test.ts +750 -0
  590. package/test/mcp/oauth-auto-connect.test.ts +199 -0
  591. package/test/mcp/oauth-browser.test.ts +249 -0
  592. package/test/memory/abort-leak.test.ts +137 -0
  593. package/test/patch/patch.test.ts +348 -0
  594. package/test/permission/arity.test.ts +33 -0
  595. package/test/permission/next.test.ts +1148 -0
  596. package/test/permission-task.test.ts +323 -0
  597. package/test/plugin/auth-override.test.ts +74 -0
  598. package/test/plugin/codex.test.ts +123 -0
  599. package/test/plugin/install-concurrency.test.ts +134 -0
  600. package/test/plugin/install.test.ts +504 -0
  601. package/test/plugin/loader-shared.test.ts +625 -0
  602. package/test/plugin/meta.test.ts +137 -0
  603. package/test/plugin/trigger.test.ts +111 -0
  604. package/test/preload.ts +90 -0
  605. package/test/project/migrate-global.test.ts +140 -0
  606. package/test/project/project.test.ts +459 -0
  607. package/test/project/state.test.ts +115 -0
  608. package/test/project/vcs.test.ts +116 -0
  609. package/test/project/worktree-remove.test.ts +96 -0
  610. package/test/project/worktree.test.ts +173 -0
  611. package/test/provider/amazon-bedrock.test.ts +447 -0
  612. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  613. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  614. package/test/provider/gitlab-duo.test.ts +412 -0
  615. package/test/provider/provider.test.ts +2284 -0
  616. package/test/provider/transform.test.ts +2758 -0
  617. package/test/pty/pty-output-isolation.test.ts +141 -0
  618. package/test/pty/pty-session.test.ts +92 -0
  619. package/test/question/question.test.ts +453 -0
  620. package/test/server/global-session-list.test.ts +89 -0
  621. package/test/server/project-init-git.test.ts +121 -0
  622. package/test/server/session-list.test.ts +90 -0
  623. package/test/server/session-messages.test.ts +132 -0
  624. package/test/server/session-select.test.ts +78 -0
  625. package/test/session/compaction.test.ts +1094 -0
  626. package/test/session/instruction.test.ts +170 -0
  627. package/test/session/llm.test.ts +882 -0
  628. package/test/session/message-v2.test.ts +957 -0
  629. package/test/session/messages-pagination.test.ts +115 -0
  630. package/test/session/processor-effect.test.ts +838 -0
  631. package/test/session/prompt.test.ts +518 -0
  632. package/test/session/retry.test.ts +232 -0
  633. package/test/session/revert-compact.test.ts +286 -0
  634. package/test/session/session.test.ts +142 -0
  635. package/test/session/structured-output-integration.test.ts +233 -0
  636. package/test/session/structured-output.test.ts +391 -0
  637. package/test/session/system.test.ts +59 -0
  638. package/test/share/share-next.test.ts +76 -0
  639. package/test/skill/discovery.test.ts +116 -0
  640. package/test/skill/skill.test.ts +392 -0
  641. package/test/snapshot/snapshot.test.ts +1235 -0
  642. package/test/storage/db.test.ts +14 -0
  643. package/test/storage/json-migration.test.ts +849 -0
  644. package/test/sync/index.test.ts +191 -0
  645. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  646. package/test/tool/apply_patch.test.ts +567 -0
  647. package/test/tool/bash.test.ts +403 -0
  648. package/test/tool/edit.test.ts +681 -0
  649. package/test/tool/external-directory.test.ts +128 -0
  650. package/test/tool/fixtures/large-image.png +0 -0
  651. package/test/tool/fixtures/models-api.json +38413 -0
  652. package/test/tool/grep.test.ts +111 -0
  653. package/test/tool/question.test.ts +108 -0
  654. package/test/tool/read.test.ts +509 -0
  655. package/test/tool/registry.test.ts +126 -0
  656. package/test/tool/skill.test.ts +167 -0
  657. package/test/tool/task.test.ts +49 -0
  658. package/test/tool/truncation.test.ts +161 -0
  659. package/test/tool/webfetch.test.ts +101 -0
  660. package/test/tool/write.test.ts +353 -0
  661. package/test/util/data-url.test.ts +14 -0
  662. package/test/util/effect-zod.test.ts +61 -0
  663. package/test/util/error.test.ts +38 -0
  664. package/test/util/filesystem.test.ts +558 -0
  665. package/test/util/flock.test.ts +383 -0
  666. package/test/util/format.test.ts +59 -0
  667. package/test/util/glob.test.ts +164 -0
  668. package/test/util/iife.test.ts +36 -0
  669. package/test/util/lazy.test.ts +50 -0
  670. package/test/util/lock.test.ts +72 -0
  671. package/test/util/module.test.ts +59 -0
  672. package/test/util/process.test.ts +128 -0
  673. package/test/util/timeout.test.ts +21 -0
  674. package/test/util/which.test.ts +100 -0
  675. package/test/util/wildcard.test.ts +90 -0
  676. package/tsconfig.json +23 -0
@@ -0,0 +1,428 @@
1
+ /**
2
+ * tk_ toolkode — Agent Teams
3
+ *
4
+ * Multi-session coordination. One lead, N teammates.
5
+ * Each teammate is a separate toolkode process with its own context.
6
+ * Shared task list + mailbox for coordination.
7
+ *
8
+ * NOT experimental. Production feature.
9
+ */
10
+ import { Log } from "../util/log"
11
+ import { Global } from "../global"
12
+ import path from "path"
13
+ import fs from "fs/promises"
14
+ import { Filesystem } from "../util/filesystem"
15
+ import { execSync, spawn, type ChildProcess } from "child_process"
16
+
17
+ const log = Log.create({ service: "team" })
18
+
19
+ export namespace Team {
20
+ /* ── Types ──────────────────────────────────────────────────── */
21
+
22
+ export interface Member {
23
+ name: string
24
+ agentId: string
25
+ agentType: string
26
+ pid?: number
27
+ status: "active" | "idle" | "stopped"
28
+ model?: string
29
+ spawnPrompt?: string
30
+ }
31
+
32
+ export interface Config {
33
+ name: string
34
+ leadSessionId: string
35
+ members: Member[]
36
+ createdAt: number
37
+ directory: string
38
+ }
39
+
40
+ export interface Task {
41
+ id: string
42
+ title: string
43
+ description: string
44
+ status: "pending" | "in_progress" | "completed"
45
+ assignee?: string
46
+ dependsOn?: string[]
47
+ createdAt: number
48
+ completedAt?: number
49
+ claimedBy?: string
50
+ }
51
+
52
+ export interface Message {
53
+ id: string
54
+ from: string
55
+ to: string | "all" // "all" = broadcast
56
+ content: string
57
+ timestamp: number
58
+ }
59
+
60
+ /* ── Paths ──────────────────────────────────────────────────── */
61
+
62
+ function teamDir(teamName: string) {
63
+ return path.join(Global.Path.data, "teams", teamName)
64
+ }
65
+
66
+ function configPath(teamName: string) {
67
+ return path.join(teamDir(teamName), "config.json")
68
+ }
69
+
70
+ function tasksDir(teamName: string) {
71
+ return path.join(Global.Path.data, "tasks", teamName)
72
+ }
73
+
74
+ function tasksPath(teamName: string) {
75
+ return path.join(tasksDir(teamName), "tasks.json")
76
+ }
77
+
78
+ function mailboxPath(teamName: string) {
79
+ return path.join(teamDir(teamName), "mailbox.json")
80
+ }
81
+
82
+ /* ── Team Lifecycle ─────────────────────────────────────────── */
83
+
84
+ const activeTeams = new Map<string, Config>()
85
+ const processes = new Map<string, ChildProcess>()
86
+
87
+ /** Create a new team */
88
+ export async function create(input: {
89
+ name: string
90
+ leadSessionId: string
91
+ directory: string
92
+ }): Promise<Config> {
93
+ const dir = teamDir(input.name)
94
+ await fs.mkdir(dir, { recursive: true })
95
+ await fs.mkdir(tasksDir(input.name), { recursive: true })
96
+
97
+ const config: Config = {
98
+ name: input.name,
99
+ leadSessionId: input.leadSessionId,
100
+ members: [],
101
+ createdAt: Date.now(),
102
+ directory: input.directory,
103
+ }
104
+
105
+ await Filesystem.write(configPath(input.name), JSON.stringify(config, null, 2))
106
+ await Filesystem.write(tasksPath(input.name), JSON.stringify([], null, 2))
107
+ await Filesystem.write(mailboxPath(input.name), JSON.stringify([], null, 2))
108
+
109
+ activeTeams.set(input.name, config)
110
+ log.info("team created", { name: input.name })
111
+ return config
112
+ }
113
+
114
+ /** Get team config */
115
+ export async function get(teamName: string): Promise<Config | null> {
116
+ const cached = activeTeams.get(teamName)
117
+ if (cached) return cached
118
+
119
+ try {
120
+ const data = await Filesystem.readJson<Config>(configPath(teamName))
121
+ if (data) activeTeams.set(teamName, data)
122
+ return data ?? null
123
+ } catch {
124
+ return null
125
+ }
126
+ }
127
+
128
+ /** List all teams */
129
+ export async function list(): Promise<string[]> {
130
+ try {
131
+ const teamsRoot = path.join(Global.Path.data, "teams")
132
+ const entries = await fs.readdir(teamsRoot).catch(() => [])
133
+ return entries
134
+ } catch {
135
+ return []
136
+ }
137
+ }
138
+
139
+ /* ── Teammate Spawning ──────────────────────────────────────── */
140
+
141
+ /** Spawn a teammate as a separate toolkode process */
142
+ export async function spawnTeammate(input: {
143
+ teamName: string
144
+ name: string
145
+ agentType: string
146
+ prompt: string
147
+ model?: string
148
+ planApproval?: boolean
149
+ }): Promise<Member> {
150
+ const config = await get(input.teamName)
151
+ if (!config) throw new Error(`Team "${input.teamName}" not found`)
152
+
153
+ const agentId = crypto.randomUUID().slice(0, 8)
154
+
155
+ const member: Member = {
156
+ name: input.name,
157
+ agentId,
158
+ agentType: input.agentType,
159
+ status: "active",
160
+ model: input.model,
161
+ spawnPrompt: input.prompt,
162
+ }
163
+
164
+ // Spawn toolkode process for this teammate
165
+ // The teammate runs as a headless session with the spawn prompt
166
+ const args = [
167
+ "run",
168
+ "--print",
169
+ "--no-input",
170
+ ]
171
+
172
+ if (input.model) args.push("--model", input.model)
173
+ if (input.planApproval) args.push("--permission-mode", "plan")
174
+
175
+ // Find the toolkode binary — process.argv[0] is the runtime (node/bun),
176
+ // process.argv[1] is the script. Prefer TOOLKODE_BIN_PATH env, then which, then argv[1].
177
+ const toolkodeBin = process.env.TOOLKODE_BIN_PATH
178
+ || (() => { try { return execSync("which toolkode", { encoding: "utf-8" }).trim() } catch { return undefined } })()
179
+ || process.argv[1]
180
+ || "toolkode"
181
+
182
+ try {
183
+ const child = spawn(toolkodeBin, [...args, "--", input.prompt], {
184
+ cwd: config.directory,
185
+ stdio: ["pipe", "pipe", "pipe"],
186
+ env: {
187
+ ...process.env,
188
+ TOOLKODE_TEAM_NAME: input.teamName,
189
+ TOOLKODE_TEAMMATE_NAME: input.name,
190
+ TOOLKODE_TEAMMATE_ID: agentId,
191
+ },
192
+ detached: false,
193
+ })
194
+
195
+ member.pid = child.pid
196
+ processes.set(agentId, child)
197
+
198
+ child.on("exit", (code) => {
199
+ member.status = "stopped"
200
+ processes.delete(agentId)
201
+ log.info("teammate exited", { name: input.name, agentId, code })
202
+ // Auto-notify lead
203
+ sendMessage({
204
+ teamName: input.teamName,
205
+ from: input.name,
206
+ to: "lead",
207
+ content: `Teammate ${input.name} has finished (exit code: ${code})`,
208
+ }).catch(() => {})
209
+ })
210
+
211
+ // Capture output for logging
212
+ child.stdout?.on("data", (data: Buffer) => {
213
+ log.info(`[${input.name}] ${data.toString().trim()}`)
214
+ })
215
+ child.stderr?.on("data", (data: Buffer) => {
216
+ log.error(`[${input.name}] ${data.toString().trim()}`)
217
+ })
218
+ } catch (err) {
219
+ log.error("failed to spawn teammate", { name: input.name, error: err })
220
+ member.status = "stopped"
221
+ }
222
+
223
+ // Update team config
224
+ config.members.push(member)
225
+ await Filesystem.write(configPath(input.teamName), JSON.stringify(config, null, 2))
226
+ activeTeams.set(input.teamName, config)
227
+
228
+ log.info("teammate spawned", { name: input.name, agentId, pid: member.pid })
229
+ return member
230
+ }
231
+
232
+ /** Shutdown a specific teammate */
233
+ export async function shutdownTeammate(teamName: string, nameOrId: string): Promise<boolean> {
234
+ const config = await get(teamName)
235
+ if (!config) return false
236
+
237
+ const member = config.members.find((m) => m.name === nameOrId || m.agentId === nameOrId)
238
+ if (!member) return false
239
+
240
+ const proc = processes.get(member.agentId)
241
+ if (proc && !proc.killed) {
242
+ proc.kill("SIGTERM")
243
+ // Give it 5 seconds, then force
244
+ setTimeout(() => {
245
+ if (!proc.killed) proc.kill("SIGKILL")
246
+ }, 5000)
247
+ }
248
+
249
+ member.status = "stopped"
250
+ await Filesystem.write(configPath(teamName), JSON.stringify(config, null, 2))
251
+ return true
252
+ }
253
+
254
+ /* ── Shared Task List ───────────────────────────────────────── */
255
+
256
+ /** Get all tasks for a team */
257
+ export async function getTasks(teamName: string): Promise<Task[]> {
258
+ try {
259
+ return (await Filesystem.readJson<Task[]>(tasksPath(teamName))) ?? []
260
+ } catch {
261
+ return []
262
+ }
263
+ }
264
+
265
+ /** Create a task */
266
+ export async function createTask(teamName: string, input: {
267
+ title: string
268
+ description: string
269
+ assignee?: string
270
+ dependsOn?: string[]
271
+ }): Promise<Task> {
272
+ const tasks = await getTasks(teamName)
273
+ const task: Task = {
274
+ id: crypto.randomUUID().slice(0, 8),
275
+ title: input.title,
276
+ description: input.description,
277
+ status: "pending",
278
+ assignee: input.assignee,
279
+ dependsOn: input.dependsOn,
280
+ createdAt: Date.now(),
281
+ }
282
+
283
+ tasks.push(task)
284
+ await Filesystem.write(tasksPath(teamName), JSON.stringify(tasks, null, 2))
285
+ log.info("task created", { teamName, taskId: task.id, title: task.title })
286
+ return task
287
+ }
288
+
289
+ /** Claim a task with file-based locking to prevent race conditions */
290
+ export async function claimTask(teamName: string, taskId: string, claimant: string): Promise<Task | null> {
291
+ // Atomic lock: create a lockfile — if it already exists, another teammate is claiming
292
+ const lockPath = path.join(tasksDir(teamName), `${taskId}.lock`)
293
+ try {
294
+ await fs.writeFile(lockPath, claimant, { flag: "wx" }) // wx = exclusive create, fails if exists
295
+ } catch {
296
+ return null // Another teammate is claiming this task
297
+ }
298
+
299
+ try {
300
+ const tasks = await getTasks(teamName)
301
+ const task = tasks.find((t) => t.id === taskId)
302
+ if (!task || task.status !== "pending") {
303
+ await fs.rm(lockPath, { force: true })
304
+ return null
305
+ }
306
+
307
+ // Check dependencies
308
+ if (task.dependsOn?.length) {
309
+ const unresolved = task.dependsOn.filter((depId) => {
310
+ const dep = tasks.find((t) => t.id === depId)
311
+ return !dep || dep.status !== "completed"
312
+ })
313
+ if (unresolved.length > 0) {
314
+ await fs.rm(lockPath, { force: true })
315
+ return null // blocked
316
+ }
317
+ }
318
+
319
+ // Claim it
320
+ task.status = "in_progress"
321
+ task.claimedBy = claimant
322
+ await Filesystem.write(tasksPath(teamName), JSON.stringify(tasks, null, 2))
323
+ log.info("task claimed", { teamName, taskId, claimant })
324
+ return task
325
+ } finally {
326
+ await fs.rm(lockPath, { force: true })
327
+ }
328
+ }
329
+
330
+ /** Complete a task */
331
+ export async function completeTask(teamName: string, taskId: string): Promise<Task | null> {
332
+ const tasks = await getTasks(teamName)
333
+ const task = tasks.find((t) => t.id === taskId)
334
+ if (!task) return null
335
+
336
+ task.status = "completed"
337
+ task.completedAt = Date.now()
338
+ await Filesystem.write(tasksPath(teamName), JSON.stringify(tasks, null, 2))
339
+ log.info("task completed", { teamName, taskId })
340
+ return task
341
+ }
342
+
343
+ /** Get next available (unblocked, unclaimed) task */
344
+ export async function getNextTask(teamName: string): Promise<Task | null> {
345
+ const tasks = await getTasks(teamName)
346
+ for (const task of tasks) {
347
+ if (task.status !== "pending") continue
348
+ if (task.dependsOn?.length) {
349
+ const allDone = task.dependsOn.every((depId) => {
350
+ const dep = tasks.find((t) => t.id === depId)
351
+ return dep?.status === "completed"
352
+ })
353
+ if (!allDone) continue
354
+ }
355
+ return task
356
+ }
357
+ return null
358
+ }
359
+
360
+ /* ── Mailbox ────────────────────────────────────────────────── */
361
+
362
+ /** Send a message to a teammate or broadcast */
363
+ export async function sendMessage(input: {
364
+ teamName: string
365
+ from: string
366
+ to: string // teammate name or "all" for broadcast
367
+ content: string
368
+ }): Promise<Message> {
369
+ const messages = await getMessages(input.teamName)
370
+ const msg: Message = {
371
+ id: crypto.randomUUID().slice(0, 8),
372
+ from: input.from,
373
+ to: input.to,
374
+ content: input.content,
375
+ timestamp: Date.now(),
376
+ }
377
+
378
+ messages.push(msg)
379
+ await Filesystem.write(mailboxPath(input.teamName), JSON.stringify(messages, null, 2))
380
+ return msg
381
+ }
382
+
383
+ /** Get all messages for a team */
384
+ export async function getMessages(teamName: string): Promise<Message[]> {
385
+ try {
386
+ return (await Filesystem.readJson<Message[]>(mailboxPath(teamName))) ?? []
387
+ } catch {
388
+ return []
389
+ }
390
+ }
391
+
392
+ /** Get messages for a specific recipient */
393
+ export async function getMessagesFor(teamName: string, recipient: string): Promise<Message[]> {
394
+ const all = await getMessages(teamName)
395
+ return all.filter((m) => m.to === recipient || m.to === "all")
396
+ }
397
+
398
+ /* ── Cleanup ────────────────────────────────────────────────── */
399
+
400
+ /** Clean up team resources */
401
+ export async function cleanup(teamName: string): Promise<{ error?: string }> {
402
+ const config = await get(teamName)
403
+ if (!config) return { error: `Team "${teamName}" not found` }
404
+
405
+ // Check for active teammates
406
+ const active = config.members.filter((m) => m.status === "active")
407
+ if (active.length > 0) {
408
+ return {
409
+ error: `Cannot clean up: ${active.length} teammates still active (${active.map((m) => m.name).join(", ")}). Shut them down first.`,
410
+ }
411
+ }
412
+
413
+ // Kill any remaining processes
414
+ for (const member of config.members) {
415
+ const proc = processes.get(member.agentId)
416
+ if (proc && !proc.killed) proc.kill("SIGKILL")
417
+ processes.delete(member.agentId)
418
+ }
419
+
420
+ // Remove files
421
+ await fs.rm(teamDir(teamName), { recursive: true, force: true })
422
+ await fs.rm(tasksDir(teamName), { recursive: true, force: true })
423
+ activeTeams.delete(teamName)
424
+
425
+ log.info("team cleaned up", { name: teamName })
426
+ return {}
427
+ }
428
+ }
@@ -0,0 +1,281 @@
1
+ import z from "zod"
2
+ import * as path from "path"
3
+ import * as fs from "fs/promises"
4
+ import { Tool } from "./tool"
5
+ import { Bus } from "../bus"
6
+ import { FileWatcher } from "../file/watcher"
7
+ import { Instance } from "../project/instance"
8
+ import { Patch } from "../patch"
9
+ import { createTwoFilesPatch, diffLines } from "diff"
10
+ import { assertExternalDirectory } from "./external-directory"
11
+ import { trimDiff } from "./edit"
12
+ import { LSP } from "../lsp"
13
+ import { Filesystem } from "../util/filesystem"
14
+ import DESCRIPTION from "./apply_patch.txt"
15
+ import { File } from "../file"
16
+ import { Format } from "../format"
17
+
18
+ const PatchParams = z.object({
19
+ patchText: z.string().describe("The full patch text that describes all changes to be made"),
20
+ })
21
+
22
+ export const ApplyPatchTool = Tool.define("apply_patch", {
23
+ description: DESCRIPTION,
24
+ parameters: PatchParams,
25
+ async execute(params, ctx) {
26
+ if (!params.patchText) {
27
+ throw new Error("patchText is required")
28
+ }
29
+
30
+ // Parse the patch to get hunks
31
+ let hunks: Patch.Hunk[]
32
+ try {
33
+ const parseResult = Patch.parsePatch(params.patchText)
34
+ hunks = parseResult.hunks
35
+ } catch (error) {
36
+ throw new Error(`apply_patch verification failed: ${error}`)
37
+ }
38
+
39
+ if (hunks.length === 0) {
40
+ const normalized = params.patchText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim()
41
+ if (normalized === "*** Begin Patch\n*** End Patch") {
42
+ throw new Error("patch rejected: empty patch")
43
+ }
44
+ throw new Error("apply_patch verification failed: no hunks found")
45
+ }
46
+
47
+ // Validate file paths and check permissions
48
+ const fileChanges: Array<{
49
+ filePath: string
50
+ oldContent: string
51
+ newContent: string
52
+ type: "add" | "update" | "delete" | "move"
53
+ movePath?: string
54
+ diff: string
55
+ additions: number
56
+ deletions: number
57
+ }> = []
58
+
59
+ let totalDiff = ""
60
+
61
+ for (const hunk of hunks) {
62
+ const filePath = path.resolve(Instance.directory, hunk.path)
63
+ await assertExternalDirectory(ctx, filePath)
64
+
65
+ switch (hunk.type) {
66
+ case "add": {
67
+ const oldContent = ""
68
+ const newContent =
69
+ hunk.contents.length === 0 || hunk.contents.endsWith("\n") ? hunk.contents : `${hunk.contents}\n`
70
+ const diff = trimDiff(createTwoFilesPatch(filePath, filePath, oldContent, newContent))
71
+
72
+ let additions = 0
73
+ let deletions = 0
74
+ for (const change of diffLines(oldContent, newContent)) {
75
+ if (change.added) additions += change.count || 0
76
+ if (change.removed) deletions += change.count || 0
77
+ }
78
+
79
+ fileChanges.push({
80
+ filePath,
81
+ oldContent,
82
+ newContent,
83
+ type: "add",
84
+ diff,
85
+ additions,
86
+ deletions,
87
+ })
88
+
89
+ totalDiff += diff + "\n"
90
+ break
91
+ }
92
+
93
+ case "update": {
94
+ // Check if file exists for update
95
+ const stats = await fs.stat(filePath).catch(() => null)
96
+ if (!stats || stats.isDirectory()) {
97
+ throw new Error(`apply_patch verification failed: Failed to read file to update: ${filePath}`)
98
+ }
99
+
100
+ const oldContent = await fs.readFile(filePath, "utf-8")
101
+ let newContent = oldContent
102
+
103
+ // Apply the update chunks to get new content
104
+ try {
105
+ const fileUpdate = Patch.deriveNewContentsFromChunks(filePath, hunk.chunks)
106
+ newContent = fileUpdate.content
107
+ } catch (error) {
108
+ throw new Error(`apply_patch verification failed: ${error}`)
109
+ }
110
+
111
+ const diff = trimDiff(createTwoFilesPatch(filePath, filePath, oldContent, newContent))
112
+
113
+ let additions = 0
114
+ let deletions = 0
115
+ for (const change of diffLines(oldContent, newContent)) {
116
+ if (change.added) additions += change.count || 0
117
+ if (change.removed) deletions += change.count || 0
118
+ }
119
+
120
+ const movePath = hunk.move_path ? path.resolve(Instance.directory, hunk.move_path) : undefined
121
+ await assertExternalDirectory(ctx, movePath)
122
+
123
+ fileChanges.push({
124
+ filePath,
125
+ oldContent,
126
+ newContent,
127
+ type: hunk.move_path ? "move" : "update",
128
+ movePath,
129
+ diff,
130
+ additions,
131
+ deletions,
132
+ })
133
+
134
+ totalDiff += diff + "\n"
135
+ break
136
+ }
137
+
138
+ case "delete": {
139
+ const contentToDelete = await fs.readFile(filePath, "utf-8").catch((error) => {
140
+ throw new Error(`apply_patch verification failed: ${error}`)
141
+ })
142
+ const deleteDiff = trimDiff(createTwoFilesPatch(filePath, filePath, contentToDelete, ""))
143
+
144
+ const deletions = contentToDelete.split("\n").length
145
+
146
+ fileChanges.push({
147
+ filePath,
148
+ oldContent: contentToDelete,
149
+ newContent: "",
150
+ type: "delete",
151
+ diff: deleteDiff,
152
+ additions: 0,
153
+ deletions,
154
+ })
155
+
156
+ totalDiff += deleteDiff + "\n"
157
+ break
158
+ }
159
+ }
160
+ }
161
+
162
+ // Build per-file metadata for UI rendering (used for both permission and result)
163
+ const files = fileChanges.map((change) => ({
164
+ filePath: change.filePath,
165
+ relativePath: path.relative(Instance.worktree, change.movePath ?? change.filePath).replaceAll("\\", "/"),
166
+ type: change.type,
167
+ diff: change.diff,
168
+ before: change.oldContent,
169
+ after: change.newContent,
170
+ additions: change.additions,
171
+ deletions: change.deletions,
172
+ movePath: change.movePath,
173
+ }))
174
+
175
+ // Check permissions if needed
176
+ const relativePaths = fileChanges.map((c) => path.relative(Instance.worktree, c.filePath).replaceAll("\\", "/"))
177
+ await ctx.ask({
178
+ permission: "edit",
179
+ patterns: relativePaths,
180
+ always: ["*"],
181
+ metadata: {
182
+ filepath: relativePaths.join(", "),
183
+ diff: totalDiff,
184
+ files,
185
+ },
186
+ })
187
+
188
+ // Apply the changes
189
+ const updates: Array<{ file: string; event: "add" | "change" | "unlink" }> = []
190
+
191
+ for (const change of fileChanges) {
192
+ const edited = change.type === "delete" ? undefined : (change.movePath ?? change.filePath)
193
+ switch (change.type) {
194
+ case "add":
195
+ // Create parent directories (recursive: true is safe on existing/root dirs)
196
+ await fs.mkdir(path.dirname(change.filePath), { recursive: true })
197
+ await fs.writeFile(change.filePath, change.newContent, "utf-8")
198
+ updates.push({ file: change.filePath, event: "add" })
199
+ break
200
+
201
+ case "update":
202
+ await fs.writeFile(change.filePath, change.newContent, "utf-8")
203
+ updates.push({ file: change.filePath, event: "change" })
204
+ break
205
+
206
+ case "move":
207
+ if (change.movePath) {
208
+ // Create parent directories (recursive: true is safe on existing/root dirs)
209
+ await fs.mkdir(path.dirname(change.movePath), { recursive: true })
210
+ await fs.writeFile(change.movePath, change.newContent, "utf-8")
211
+ await fs.unlink(change.filePath)
212
+ updates.push({ file: change.filePath, event: "unlink" })
213
+ updates.push({ file: change.movePath, event: "add" })
214
+ }
215
+ break
216
+
217
+ case "delete":
218
+ await fs.unlink(change.filePath)
219
+ updates.push({ file: change.filePath, event: "unlink" })
220
+ break
221
+ }
222
+
223
+ if (edited) {
224
+ await Format.file(edited)
225
+ Bus.publish(File.Event.Edited, { file: edited })
226
+ }
227
+ }
228
+
229
+ // Publish file change events
230
+ for (const update of updates) {
231
+ await Bus.publish(FileWatcher.Event.Updated, update)
232
+ }
233
+
234
+ // Notify LSP of file changes and collect diagnostics
235
+ for (const change of fileChanges) {
236
+ if (change.type === "delete") continue
237
+ const target = change.movePath ?? change.filePath
238
+ await LSP.touchFile(target, true)
239
+ }
240
+ const diagnostics = await LSP.diagnostics()
241
+
242
+ // Generate output summary
243
+ const summaryLines = fileChanges.map((change) => {
244
+ if (change.type === "add") {
245
+ return `A ${path.relative(Instance.worktree, change.filePath).replaceAll("\\", "/")}`
246
+ }
247
+ if (change.type === "delete") {
248
+ return `D ${path.relative(Instance.worktree, change.filePath).replaceAll("\\", "/")}`
249
+ }
250
+ const target = change.movePath ?? change.filePath
251
+ return `M ${path.relative(Instance.worktree, target).replaceAll("\\", "/")}`
252
+ })
253
+ let output = `Success. Updated the following files:\n${summaryLines.join("\n")}`
254
+
255
+ // Report LSP errors for changed files
256
+ const MAX_DIAGNOSTICS_PER_FILE = 20
257
+ for (const change of fileChanges) {
258
+ if (change.type === "delete") continue
259
+ const target = change.movePath ?? change.filePath
260
+ const normalized = Filesystem.normalizePath(target)
261
+ const issues = diagnostics[normalized] ?? []
262
+ const errors = issues.filter((item) => item.severity === 1)
263
+ if (errors.length > 0) {
264
+ const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
265
+ const suffix =
266
+ errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
267
+ output += `\n\nLSP errors detected in ${path.relative(Instance.worktree, target).replaceAll("\\", "/")}, please fix:\n<diagnostics file="${target}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
268
+ }
269
+ }
270
+
271
+ return {
272
+ title: output,
273
+ metadata: {
274
+ diff: totalDiff,
275
+ files,
276
+ diagnostics,
277
+ },
278
+ output,
279
+ }
280
+ },
281
+ })