tower-studio 0.1.2 → 0.1.3

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 (641) hide show
  1. package/bin/tower.mjs +5 -15
  2. package/next.config.ts +1 -8
  3. package/package.json +10 -11
  4. package/prisma/dev.db +0 -0
  5. package/public/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
  6. package/public/vs/abap-D-t0cyap.js +1 -0
  7. package/public/vs/apex-CcIm7xu6.js +1 -0
  8. package/public/vs/assets/css.worker-HnVq6Ewq.js +93 -0
  9. package/public/vs/assets/editor.worker-Be8ye1pW.js +26 -0
  10. package/public/vs/assets/html.worker-B51mlPHg.js +470 -0
  11. package/public/vs/assets/json.worker-DKiEKt88.js +58 -0
  12. package/public/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
  13. package/public/vs/azcli-BA0tQDCg.js +1 -0
  14. package/public/vs/basic-languages/monaco.contribution.js +1 -0
  15. package/public/vs/bat-C397hTD6.js +1 -0
  16. package/public/vs/bicep-DF5aW17k.js +2 -0
  17. package/public/vs/cameligo-plsz8qhj.js +1 -0
  18. package/public/vs/clojure-Y2auQMzK.js +1 -0
  19. package/public/vs/coffee-Bu45yuWE.js +1 -0
  20. package/public/vs/cpp-CkKPQIni.js +1 -0
  21. package/public/vs/csharp-CX28MZyh.js +1 -0
  22. package/public/vs/csp-D8uWnyxW.js +1 -0
  23. package/public/vs/css-CaeNmE3S.js +3 -0
  24. package/public/vs/cssMode-CjiAH6dQ.js +1 -0
  25. package/public/vs/cypher-DVThT8BS.js +1 -0
  26. package/public/vs/dart-CmGfCvrO.js +1 -0
  27. package/public/vs/dockerfile-CZqqYdch.js +1 -0
  28. package/public/vs/ecl-30fUercY.js +1 -0
  29. package/public/vs/editor/editor.main.css +1 -0
  30. package/public/vs/editor/editor.main.js +5 -0
  31. package/public/vs/editor.api-CalNCsUg.js +903 -0
  32. package/public/vs/elixir-xjPaIfzF.js +1 -0
  33. package/public/vs/flow9-DqtmStfK.js +1 -0
  34. package/public/vs/freemarker2-Cz_sV6Md.js +3 -0
  35. package/public/vs/fsharp-BOMdg4U1.js +1 -0
  36. package/public/vs/go-D_hbi-Jt.js +1 -0
  37. package/public/vs/graphql-CKUU4kLG.js +1 -0
  38. package/public/vs/handlebars-OwglfO-1.js +1 -0
  39. package/public/vs/hcl-DTaboeZW.js +1 -0
  40. package/public/vs/html-Pa1xEWsY.js +1 -0
  41. package/public/vs/htmlMode-Bz67EXwp.js +1 -0
  42. package/public/vs/ini-CsNwO04R.js +1 -0
  43. package/public/vs/java-CI4ZMsH9.js +1 -0
  44. package/public/vs/javascript-PczUCGdz.js +1 -0
  45. package/public/vs/jsonMode-DULH5oaX.js +7 -0
  46. package/public/vs/julia-BwzEvaQw.js +1 -0
  47. package/public/vs/kotlin-IUYPiTV8.js +1 -0
  48. package/public/vs/language/css/monaco.contribution.js +1 -0
  49. package/public/vs/language/html/monaco.contribution.js +1 -0
  50. package/public/vs/language/json/monaco.contribution.js +1 -0
  51. package/public/vs/language/typescript/monaco.contribution.js +1 -0
  52. package/public/vs/less-C0eDYdqa.js +2 -0
  53. package/public/vs/lexon-iON-Kj97.js +1 -0
  54. package/public/vs/liquid-DqKjdPGy.js +1 -0
  55. package/public/vs/loader.js +1368 -0
  56. package/public/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
  57. package/public/vs/lua-DtygF91M.js +1 -0
  58. package/public/vs/m3-CsR4AuFi.js +1 -0
  59. package/public/vs/markdown-C_rD0bIw.js +1 -0
  60. package/public/vs/mdx-DEWtB1K5.js +1 -0
  61. package/public/vs/mips-CiYP61RB.js +1 -0
  62. package/public/vs/monaco.contribution-D2OdxNBt.js +1 -0
  63. package/public/vs/monaco.contribution-DO3azKX8.js +1 -0
  64. package/public/vs/monaco.contribution-EcChJV6a.js +1 -0
  65. package/public/vs/monaco.contribution-qLAYrEOP.js +1 -0
  66. package/public/vs/msdax-C38-sJlp.js +1 -0
  67. package/public/vs/mysql-CdtbpvbG.js +1 -0
  68. package/public/vs/nls.messages-loader.js +1 -0
  69. package/public/vs/nls.messages.cs.js.js +17 -0
  70. package/public/vs/nls.messages.de.js.js +17 -0
  71. package/public/vs/nls.messages.es.js.js +17 -0
  72. package/public/vs/nls.messages.fr.js.js +15 -0
  73. package/public/vs/nls.messages.it.js.js +15 -0
  74. package/public/vs/nls.messages.ja.js.js +17 -0
  75. package/public/vs/nls.messages.js.js +10 -0
  76. package/public/vs/nls.messages.ko.js.js +25 -0
  77. package/public/vs/nls.messages.pl.js.js +17 -0
  78. package/public/vs/nls.messages.pt-br.js.js +6 -0
  79. package/public/vs/nls.messages.ru.js.js +17 -0
  80. package/public/vs/nls.messages.tr.js.js +15 -0
  81. package/public/vs/nls.messages.zh-cn.js.js +17 -0
  82. package/public/vs/nls.messages.zh-tw.js.js +15 -0
  83. package/public/vs/objective-c-CntZFaHX.js +1 -0
  84. package/public/vs/pascal-r6kuqfl_.js +1 -0
  85. package/public/vs/pascaligo-BiXoTmXh.js +1 -0
  86. package/public/vs/perl-DABw_TcH.js +1 -0
  87. package/public/vs/pgsql-me_jFXeX.js +1 -0
  88. package/public/vs/php-D_kh-9LK.js +1 -0
  89. package/public/vs/pla-VfZjczW0.js +1 -0
  90. package/public/vs/postiats-BBSzz8Pk.js +1 -0
  91. package/public/vs/powerquery-Dt-g_2cc.js +1 -0
  92. package/public/vs/powershell-B-7ap1zc.js +1 -0
  93. package/public/vs/protobuf-BmtuEB1A.js +2 -0
  94. package/public/vs/pug-BRpRNeEb.js +1 -0
  95. package/public/vs/python-Cr0UkIbn.js +1 -0
  96. package/public/vs/qsharp-BzsFaUU9.js +1 -0
  97. package/public/vs/r-f8dDdrp4.js +1 -0
  98. package/public/vs/razor-BYAHOTkz.js +1 -0
  99. package/public/vs/redis-fvZQY4PI.js +1 -0
  100. package/public/vs/redshift-45Et0LQi.js +1 -0
  101. package/public/vs/restructuredtext-C7UUFKFD.js +1 -0
  102. package/public/vs/ruby-CZO8zYTz.js +1 -0
  103. package/public/vs/rust-Bfetafyc.js +1 -0
  104. package/public/vs/sb-3GYllVck.js +1 -0
  105. package/public/vs/scala-foMgrKo1.js +1 -0
  106. package/public/vs/scheme-CHdMtr7p.js +1 -0
  107. package/public/vs/scss-C1cmLt9V.js +3 -0
  108. package/public/vs/shell-ClXCKCEW.js +1 -0
  109. package/public/vs/solidity-MZ6ExpPy.js +1 -0
  110. package/public/vs/sophia-DWkuSsPQ.js +1 -0
  111. package/public/vs/sparql-AUGFYSyk.js +1 -0
  112. package/public/vs/sql-32GpJSV2.js +1 -0
  113. package/public/vs/st-CuDFIVZ_.js +1 -0
  114. package/public/vs/swift-n-2HociN.js +3 -0
  115. package/public/vs/systemverilog-Ch4vA8Yt.js +1 -0
  116. package/public/vs/tcl-D74tq1nH.js +1 -0
  117. package/public/vs/tsMode-CZz1Umrk.js +11 -0
  118. package/public/vs/twig-C6taOxMV.js +1 -0
  119. package/public/vs/typescript-DfOrAzoV.js +1 -0
  120. package/public/vs/typespec-D-PIh9Xw.js +1 -0
  121. package/public/vs/vb-Dyb2648j.js +1 -0
  122. package/public/vs/wgsl-BhLXMOR0.js +298 -0
  123. package/public/vs/workers-DcJshg-q.js +1 -0
  124. package/public/vs/xml-CdsdnY8S.js +1 -0
  125. package/public/vs/yaml-DYGvmE88.js +1 -0
  126. package/src/actions/__tests__/agent-actions-username.test.ts +30 -0
  127. package/src/actions/__tests__/asset-actions.test.ts +251 -0
  128. package/src/actions/__tests__/assistant-actions.test.ts +20 -0
  129. package/src/actions/__tests__/cli-profile-actions.test.ts +243 -0
  130. package/src/actions/__tests__/label-actions.test.ts +187 -0
  131. package/src/actions/__tests__/note-actions.test.ts +237 -0
  132. package/src/actions/__tests__/onboarding-actions.test.ts +265 -0
  133. package/src/actions/__tests__/project-actions.test.ts +179 -0
  134. package/src/actions/__tests__/prompt-actions.test.ts +213 -0
  135. package/src/actions/__tests__/report-actions.test.ts +246 -0
  136. package/src/actions/__tests__/search-code-actions.test.ts +308 -0
  137. package/src/actions/__tests__/task-actions-overview.test.ts +58 -0
  138. package/src/actions/__tests__/task-actions-pin.test.ts +79 -0
  139. package/src/actions/__tests__/workspace-actions.test.ts +256 -0
  140. package/src/components/layout/__tests__/top-bar-username.test.tsx +24 -0
  141. package/src/components/onboarding/__tests__/onboarding-wizard.test.tsx +185 -0
  142. package/src/hooks/__tests__/sse-event-reducer.test.ts +263 -0
  143. package/src/hooks/__tests__/use-assistant-chat.test.ts +34 -0
  144. package/src/hooks/__tests__/use-image-upload.test.ts +443 -0
  145. package/src/lib/__tests__/assistant-message-converter.test.ts +162 -0
  146. package/src/lib/__tests__/assistant-sessions.test.ts +253 -0
  147. package/src/lib/__tests__/build-multimodal-prompt.test.ts +173 -0
  148. package/src/lib/__tests__/config-reader.test.ts +75 -0
  149. package/src/lib/__tests__/diff-parser.test.ts +212 -0
  150. package/src/lib/__tests__/execution-summary.test.ts +237 -0
  151. package/src/lib/__tests__/file-serve.test.ts +178 -0
  152. package/src/lib/__tests__/file-utils.test.ts +177 -0
  153. package/src/lib/__tests__/internal-api-guard.test.ts +151 -0
  154. package/src/lib/__tests__/logger.test.ts +181 -0
  155. package/src/lib/__tests__/platform.test.ts +566 -0
  156. package/src/lib/__tests__/reveal-route-security.test.ts +65 -0
  157. package/src/lib/__tests__/schemas.test.ts +377 -0
  158. package/src/lib/__tests__/terminal-link-provider.test.ts +160 -0
  159. package/src/lib/__tests__/upload-route-security.test.ts +120 -0
  160. package/src/lib/ai/__tests__/capability-resolver.test.ts +71 -0
  161. package/src/lib/ai/__tests__/claude-cli-adapter.test.ts +103 -0
  162. package/src/lib/ai/__tests__/provider-registry.test.ts +74 -0
  163. package/src/lib/pty/__tests__/ws-server-assistant.test.ts +7 -0
  164. package/.next/standalone/tower/.claude/rules/process-lifecycle.md +0 -31
  165. package/.next/standalone/tower/.claude/rules/security.md +0 -29
  166. package/.next/standalone/tower/.claude/rules/ui.md +0 -105
  167. package/.next/standalone/tower/AGENTS.md +0 -325
  168. package/.next/standalone/tower/CHANGELOG.md +0 -63
  169. package/.next/standalone/tower/CLAUDE.md +0 -33
  170. package/.next/standalone/tower/LICENSE +0 -21
  171. package/.next/standalone/tower/README.md +0 -214
  172. package/.next/standalone/tower/README.zh.md +0 -212
  173. package/.next/standalone/tower/bin/tower.mjs +0 -180
  174. package/.next/standalone/tower/docs/README.md +0 -51
  175. package/.next/standalone/tower/docs/ai/README.md +0 -91
  176. package/.next/standalone/tower/docs/ai/cli-abstraction-design.md +0 -398
  177. package/.next/standalone/tower/docs/ai/cli-abstraction-plan.md +0 -1345
  178. package/.next/standalone/tower/docs/assets-notes/README.md +0 -41
  179. package/.next/standalone/tower/docs/assistant/README.md +0 -48
  180. package/.next/standalone/tower/docs/board/README.md +0 -36
  181. package/.next/standalone/tower/docs/diagrams/tower-ai-architecture-en.html +0 -259
  182. package/.next/standalone/tower/docs/diagrams/tower-ai-architecture.html +0 -259
  183. package/.next/standalone/tower/docs/diagrams/tower-data-model-en.html +0 -344
  184. package/.next/standalone/tower/docs/diagrams/tower-data-model.html +0 -344
  185. package/.next/standalone/tower/docs/diagrams/tower-module-map-en.html +0 -176
  186. package/.next/standalone/tower/docs/diagrams/tower-module-map.html +0 -176
  187. package/.next/standalone/tower/docs/diagrams/tower-system-architecture-en.html +0 -336
  188. package/.next/standalone/tower/docs/diagrams/tower-system-architecture.html +0 -336
  189. package/.next/standalone/tower/docs/diagrams/tower-task-lifecycle-en.html +0 -251
  190. package/.next/standalone/tower/docs/diagrams/tower-task-lifecycle.html +0 -251
  191. package/.next/standalone/tower/docs/en/guide/architecture.md +0 -79
  192. package/.next/standalone/tower/docs/en/guide/diagrams.md +0 -24
  193. package/.next/standalone/tower/docs/en/guide/getting-started.md +0 -80
  194. package/.next/standalone/tower/docs/en/guide/introduction.md +0 -84
  195. package/.next/standalone/tower/docs/en/index.md +0 -36
  196. package/.next/standalone/tower/docs/en/modules/ai.md +0 -110
  197. package/.next/standalone/tower/docs/en/modules/assets-notes.md +0 -56
  198. package/.next/standalone/tower/docs/en/modules/assistant.md +0 -63
  199. package/.next/standalone/tower/docs/en/modules/board.md +0 -44
  200. package/.next/standalone/tower/docs/en/modules/git.md +0 -55
  201. package/.next/standalone/tower/docs/en/modules/i18n.md +0 -35
  202. package/.next/standalone/tower/docs/en/modules/mcp.md +0 -62
  203. package/.next/standalone/tower/docs/en/modules/missions.md +0 -47
  204. package/.next/standalone/tower/docs/en/modules/project.md +0 -57
  205. package/.next/standalone/tower/docs/en/modules/search.md +0 -49
  206. package/.next/standalone/tower/docs/en/modules/settings.md +0 -63
  207. package/.next/standalone/tower/docs/en/modules/task.md +0 -82
  208. package/.next/standalone/tower/docs/en/modules/terminal.md +0 -73
  209. package/.next/standalone/tower/docs/en/modules/workspace.md +0 -61
  210. package/.next/standalone/tower/docs/git/README.md +0 -42
  211. package/.next/standalone/tower/docs/guide/architecture.md +0 -29
  212. package/.next/standalone/tower/docs/guide/diagrams.md +0 -24
  213. package/.next/standalone/tower/docs/guide/getting-started.md +0 -80
  214. package/.next/standalone/tower/docs/guide/introduction.md +0 -84
  215. package/.next/standalone/tower/docs/i18n/README.md +0 -23
  216. package/.next/standalone/tower/docs/index.md +0 -36
  217. package/.next/standalone/tower/docs/mcp/README.md +0 -50
  218. package/.next/standalone/tower/docs/missions/README.md +0 -40
  219. package/.next/standalone/tower/docs/modules/ai.md +0 -104
  220. package/.next/standalone/tower/docs/modules/assets-notes.md +0 -68
  221. package/.next/standalone/tower/docs/modules/assistant.md +0 -74
  222. package/.next/standalone/tower/docs/modules/board.md +0 -56
  223. package/.next/standalone/tower/docs/modules/git.md +0 -61
  224. package/.next/standalone/tower/docs/modules/i18n.md +0 -38
  225. package/.next/standalone/tower/docs/modules/mcp.md +0 -68
  226. package/.next/standalone/tower/docs/modules/missions.md +0 -54
  227. package/.next/standalone/tower/docs/modules/project.md +0 -65
  228. package/.next/standalone/tower/docs/modules/search.md +0 -57
  229. package/.next/standalone/tower/docs/modules/settings.md +0 -72
  230. package/.next/standalone/tower/docs/modules/task.md +0 -82
  231. package/.next/standalone/tower/docs/modules/terminal.md +0 -70
  232. package/.next/standalone/tower/docs/modules/workspace.md +0 -68
  233. package/.next/standalone/tower/docs/package.json +0 -11
  234. package/.next/standalone/tower/docs/pnpm-lock.yaml +0 -1620
  235. package/.next/standalone/tower/docs/postcss.config.mjs +0 -2
  236. package/.next/standalone/tower/docs/project/README.md +0 -45
  237. package/.next/standalone/tower/docs/public/banner.png +0 -0
  238. package/.next/standalone/tower/docs/public/diagrams/tower-ai-architecture-en.html +0 -259
  239. package/.next/standalone/tower/docs/public/diagrams/tower-ai-architecture.html +0 -259
  240. package/.next/standalone/tower/docs/public/diagrams/tower-data-model-en.html +0 -344
  241. package/.next/standalone/tower/docs/public/diagrams/tower-data-model.html +0 -344
  242. package/.next/standalone/tower/docs/public/diagrams/tower-module-map-en.html +0 -176
  243. package/.next/standalone/tower/docs/public/diagrams/tower-module-map.html +0 -176
  244. package/.next/standalone/tower/docs/public/diagrams/tower-system-architecture-en.html +0 -336
  245. package/.next/standalone/tower/docs/public/diagrams/tower-system-architecture.html +0 -336
  246. package/.next/standalone/tower/docs/public/diagrams/tower-task-lifecycle-en.html +0 -251
  247. package/.next/standalone/tower/docs/public/diagrams/tower-task-lifecycle.html +0 -251
  248. package/.next/standalone/tower/docs/search/README.md +0 -37
  249. package/.next/standalone/tower/docs/settings/README.md +0 -43
  250. package/.next/standalone/tower/docs/task/README.md +0 -67
  251. package/.next/standalone/tower/docs/terminal/README.md +0 -58
  252. package/.next/standalone/tower/docs/workspace/README.md +0 -50
  253. package/.next/standalone/tower/docs/zh/guide/architecture.md +0 -29
  254. package/.next/standalone/tower/docs/zh/guide/diagrams.md +0 -24
  255. package/.next/standalone/tower/docs/zh/guide/getting-started.md +0 -80
  256. package/.next/standalone/tower/docs/zh/guide/introduction.md +0 -66
  257. package/.next/standalone/tower/docs/zh/index.md +0 -23
  258. package/.next/standalone/tower/docs/zh/modules/ai.md +0 -104
  259. package/.next/standalone/tower/docs/zh/modules/assets-notes.md +0 -68
  260. package/.next/standalone/tower/docs/zh/modules/assistant.md +0 -74
  261. package/.next/standalone/tower/docs/zh/modules/board.md +0 -56
  262. package/.next/standalone/tower/docs/zh/modules/git.md +0 -61
  263. package/.next/standalone/tower/docs/zh/modules/i18n.md +0 -38
  264. package/.next/standalone/tower/docs/zh/modules/mcp.md +0 -68
  265. package/.next/standalone/tower/docs/zh/modules/missions.md +0 -54
  266. package/.next/standalone/tower/docs/zh/modules/project.md +0 -65
  267. package/.next/standalone/tower/docs/zh/modules/search.md +0 -57
  268. package/.next/standalone/tower/docs/zh/modules/settings.md +0 -72
  269. package/.next/standalone/tower/docs/zh/modules/task.md +0 -82
  270. package/.next/standalone/tower/docs/zh/modules/terminal.md +0 -70
  271. package/.next/standalone/tower/docs/zh/modules/workspace.md +0 -68
  272. package/.next/standalone/tower/eslint.config.mjs +0 -18
  273. package/.next/standalone/tower/next.config.ts +0 -15
  274. package/.next/standalone/tower/package.json +0 -117
  275. package/.next/standalone/tower/playwright.config.ts +0 -28
  276. package/.next/standalone/tower/pnpm-lock.yaml +0 -10524
  277. package/.next/standalone/tower/pnpm-workspace.yaml +0 -6
  278. package/.next/standalone/tower/prisma/init-fts.ts +0 -23
  279. package/.next/standalone/tower/prisma/schema.prisma +0 -290
  280. package/.next/standalone/tower/prompts/backend-developer.md +0 -20
  281. package/.next/standalone/tower/prompts/code-reviewer.md +0 -19
  282. package/.next/standalone/tower/prompts/frontend-developer.md +0 -20
  283. package/.next/standalone/tower/prompts/product-manager.md +0 -16
  284. package/.next/standalone/tower/public/banner.jpg +0 -0
  285. package/.next/standalone/tower/public/logo.png +0 -0
  286. package/.next/standalone/tower/scripts/copy-monaco.js +0 -27
  287. package/.next/standalone/tower/scripts/init-db.ts +0 -74
  288. package/.next/standalone/tower/scripts/migrate-data.ts +0 -102
  289. package/.next/standalone/tower/server.js +0 -38
  290. package/.next/standalone/tower/src/app/favicon.ico +0 -0
  291. package/.next/standalone/tower/src/mcp/db.ts +0 -11
  292. package/.next/standalone/tower/src/mcp/index.ts +0 -15
  293. package/.next/standalone/tower/src/mcp/server.ts +0 -52
  294. package/.next/standalone/tower/src/mcp/tools/knowledge-tools.ts +0 -100
  295. package/.next/standalone/tower/src/mcp/tools/label-tools.ts +0 -70
  296. package/.next/standalone/tower/src/mcp/tools/note-asset-tools.ts +0 -271
  297. package/.next/standalone/tower/src/mcp/tools/project-tools.ts +0 -79
  298. package/.next/standalone/tower/src/mcp/tools/report-tools.ts +0 -214
  299. package/.next/standalone/tower/src/mcp/tools/search-tools.ts +0 -32
  300. package/.next/standalone/tower/src/mcp/tools/task-tools.ts +0 -250
  301. package/.next/standalone/tower/src/mcp/tools/terminal-tools.ts +0 -154
  302. package/.next/standalone/tower/src/mcp/tools/workspace-tools.ts +0 -73
  303. package/.next/standalone/tower/tests/e2e/chat-flow.spec.ts +0 -249
  304. package/.next/standalone/tower/tests/e2e/notes-assets.spec.ts +0 -287
  305. package/.next/standalone/tower/tests/e2e/search.spec.ts +0 -186
  306. package/.next/standalone/tower/tests/e2e/settings-flow.spec.ts +0 -250
  307. package/.next/standalone/tower/tests/e2e/settings.spec.ts +0 -261
  308. package/.next/standalone/tower/tests/e2e/smoke.spec.ts +0 -317
  309. package/.next/standalone/tower/tests/e2e/task-flow.spec.ts +0 -287
  310. package/.next/standalone/tower/tests/e2e/workbench.spec.ts +0 -307
  311. package/.next/standalone/tower/tests/setup.ts +0 -15
  312. package/.next/standalone/tower/tests/unit/actions/agent-actions.test.ts +0 -157
  313. package/.next/standalone/tower/tests/unit/actions/config-actions.test.ts +0 -170
  314. package/.next/standalone/tower/tests/unit/actions/file-actions.test.ts +0 -283
  315. package/.next/standalone/tower/tests/unit/actions/git-actions.test.ts +0 -34
  316. package/.next/standalone/tower/tests/unit/actions/preview-actions.test.ts +0 -177
  317. package/.next/standalone/tower/tests/unit/actions/search-actions.test.ts +0 -358
  318. package/.next/standalone/tower/tests/unit/actions/task-actions.test.ts +0 -173
  319. package/.next/standalone/tower/tests/unit/api/diff-route.test.ts +0 -9
  320. package/.next/standalone/tower/tests/unit/api/file-serving.test.ts +0 -88
  321. package/.next/standalone/tower/tests/unit/api/merge-route.test.ts +0 -9
  322. package/.next/standalone/tower/tests/unit/api/stream-persist-result.test.ts +0 -7
  323. package/.next/standalone/tower/tests/unit/api/stream-send-back.test.ts +0 -7
  324. package/.next/standalone/tower/tests/unit/components/asset-item.test.tsx +0 -105
  325. package/.next/standalone/tower/tests/unit/components/asset-list.test.tsx +0 -51
  326. package/.next/standalone/tower/tests/unit/components/assets/asset-upload.test.tsx +0 -70
  327. package/.next/standalone/tower/tests/unit/components/board-stats.test.tsx +0 -32
  328. package/.next/standalone/tower/tests/unit/components/category-filter.test.tsx +0 -58
  329. package/.next/standalone/tower/tests/unit/components/cli-adapter-tester.test.tsx +0 -160
  330. package/.next/standalone/tower/tests/unit/components/create-task-dialog.test.tsx +0 -162
  331. package/.next/standalone/tower/tests/unit/components/file-tree.test.tsx +0 -172
  332. package/.next/standalone/tower/tests/unit/components/note-card.test.tsx +0 -71
  333. package/.next/standalone/tower/tests/unit/components/note-editor.test.tsx +0 -39
  334. package/.next/standalone/tower/tests/unit/components/prompts-config.test.tsx +0 -200
  335. package/.next/standalone/tower/tests/unit/components/search-dialog.test.tsx +0 -223
  336. package/.next/standalone/tower/tests/unit/components/system-config.test.tsx +0 -32
  337. package/.next/standalone/tower/tests/unit/hooks/hook-registration.test.ts +0 -89
  338. package/.next/standalone/tower/tests/unit/hooks/stop-hook.test.ts +0 -108
  339. package/.next/standalone/tower/tests/unit/lib/asset-actions.test.ts +0 -257
  340. package/.next/standalone/tower/tests/unit/lib/file-utils.test.ts +0 -102
  341. package/.next/standalone/tower/tests/unit/lib/fs-security.test.ts +0 -34
  342. package/.next/standalone/tower/tests/unit/lib/fts.test.ts +0 -214
  343. package/.next/standalone/tower/tests/unit/lib/git-url.test.ts +0 -258
  344. package/.next/standalone/tower/tests/unit/lib/instrumentation.test.ts +0 -86
  345. package/.next/standalone/tower/tests/unit/lib/local-path-to-api-url.test.ts +0 -40
  346. package/.next/standalone/tower/tests/unit/lib/mime-magic.test.ts +0 -95
  347. package/.next/standalone/tower/tests/unit/lib/note-actions.test.ts +0 -296
  348. package/.next/standalone/tower/tests/unit/lib/preview-process-manager.test.ts +0 -107
  349. package/.next/standalone/tower/tests/unit/lib/process-manager.test.ts +0 -10
  350. package/.next/standalone/tower/tests/unit/lib/pty-session.test.ts +0 -171
  351. package/.next/standalone/tower/tests/unit/lib/search.test.ts +0 -289
  352. package/.next/standalone/tower/tests/unit/lib/session-store.test.ts +0 -111
  353. package/.next/standalone/tower/tests/unit/lib/utils.test.ts +0 -33
  354. package/.next/standalone/tower/tests/unit/lib/worktree.test.ts +0 -190
  355. package/.next/standalone/tower/tests/unit/mcp/identify-project.test.ts +0 -217
  356. package/.next/standalone/tower/tests/unit/mcp/manage-assets.test.ts +0 -199
  357. package/.next/standalone/tower/tests/unit/mcp/manage-notes.test.ts +0 -304
  358. package/.next/standalone/tower/tests/unit/mcp/search-tools.test.ts +0 -186
  359. package/.next/standalone/tower/tests/unit/missions-merge.test.ts +0 -43
  360. package/.next/standalone/tower/tower-studio-0.1.1.tgz +0 -0
  361. package/.next/standalone/tower/tsconfig.json +0 -34
  362. package/.next/standalone/tower/vitest.config.ts +0 -17
  363. package/.next/static/a6HR9cTK7s4aKfE5dWzjz/_buildManifest.js +0 -11
  364. package/.next/static/a6HR9cTK7s4aKfE5dWzjz/_clientMiddlewareManifest.js +0 -1
  365. package/.next/static/a6HR9cTK7s4aKfE5dWzjz/_ssgManifest.js +0 -1
  366. package/.next/static/chunks/00z-g3x93ngvn.js +0 -1
  367. package/.next/static/chunks/024pdsc27tjdd.js +0 -5
  368. package/.next/static/chunks/02dd83gbiv778.js +0 -1
  369. package/.next/static/chunks/03e.4ymu.j5wl.js +0 -1
  370. package/.next/static/chunks/03~yq9q893hmn.js +0 -1
  371. package/.next/static/chunks/05-b9qqm3av9~.js +0 -1
  372. package/.next/static/chunks/05~v02mkan5z..js +0 -1
  373. package/.next/static/chunks/0abtpeymj-58i.js +0 -1
  374. package/.next/static/chunks/0b6us7uq72u-d.js +0 -1
  375. package/.next/static/chunks/0drgc-oztq6o-.css +0 -1
  376. package/.next/static/chunks/0eaa2lmymh2fx.js +0 -1
  377. package/.next/static/chunks/0gb82g.6g90mn.js +0 -1
  378. package/.next/static/chunks/0gnm22yv~f54b.js +0 -1
  379. package/.next/static/chunks/0j9qriqni_r1..js +0 -2
  380. package/.next/static/chunks/0k.u8sxy~e469.js +0 -4
  381. package/.next/static/chunks/0lnhjf2a~jaco.js +0 -83
  382. package/.next/static/chunks/0lvd52mjiit6s.js +0 -1
  383. package/.next/static/chunks/0mq0uqbbbb1~2.js +0 -1
  384. package/.next/static/chunks/0neevhl_o1ozu.css +0 -2
  385. package/.next/static/chunks/0omj~p3uxkic-.js +0 -1
  386. package/.next/static/chunks/0t-gr6j-c65qb.js +0 -1
  387. package/.next/static/chunks/0t16ai99uv4j3.js +0 -1
  388. package/.next/static/chunks/0tcl81ybuob5i.js +0 -1
  389. package/.next/static/chunks/0uqimvsni_op~.js +0 -1
  390. package/.next/static/chunks/0wt3kws~_yr8z.js +0 -1
  391. package/.next/static/chunks/0xzdu87n_for1.js +0 -5
  392. package/.next/static/chunks/0y0tdl.rl6v1u.js +0 -1
  393. package/.next/static/chunks/0z2bzovqhl2f5.js +0 -1
  394. package/.next/static/chunks/0z4y0x1ifgy.e.js +0 -1
  395. package/.next/static/chunks/0z7bwntvfhxzi.js +0 -12
  396. package/.next/static/chunks/102ijqpvi0z-d.js +0 -1
  397. package/.next/static/chunks/10n23t.1hpb-1.js +0 -1
  398. package/.next/static/chunks/11_e3-j5gzbj4.js +0 -1
  399. package/.next/static/chunks/14.ims4y7osot.js +0 -1
  400. package/.next/static/chunks/14xzmrt5ly6gq.js +0 -31
  401. package/.next/static/chunks/151wr~6x8aclx.js +0 -1
  402. package/.next/static/chunks/169po6_~f3-d5.js +0 -1
  403. package/.next/static/chunks/16w-ap~msrwpj.js +0 -1
  404. package/.next/static/chunks/176n7f13ve~a9.js +0 -1
  405. package/.next/static/chunks/17oc2l.ekcs8b.css +0 -1
  406. package/.next/static/chunks/turbopack-0wjmrsi.z32s0.js +0 -1
  407. package/.next/static/media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2 +0 -0
  408. package/.next/static/media/7178b3e590c64307-s.11.cyxs5p-0z~.woff2 +0 -0
  409. package/.next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2 +0 -0
  410. package/.next/static/media/8a480f0b521d4e75-s.06d3mdzz5bre_.woff2 +0 -0
  411. package/.next/static/media/apple-icon.16aocl-s-v2qz.png +0 -0
  412. package/.next/static/media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2 +0 -0
  413. package/.next/static/media/caa3a2e1cccd8315-s.p.16t1db8_9y2o~.woff2 +0 -0
  414. package/.next/static/media/favicon.0y2d6j9cou~8p.ico +0 -0
  415. package/.next/static/media/icon0.0a6mkq6meyird.svg +0 -1
  416. package/.next/static/media/icon1.04ux133882seb.png +0 -0
  417. /package/{.next/standalone/tower/components.json → components.json} +0 -0
  418. /package/{.next/standalone/tower/postcss.config.mjs → postcss.config.mjs} +0 -0
  419. /package/{.next/standalone/tower/prisma → prisma}/prisma/dev.db +0 -0
  420. /package/{.next/standalone/tower/prisma → prisma}/seed.ts +0 -0
  421. /package/{.next/standalone/tower/docs/public → public}/banner.jpg +0 -0
  422. /package/{.next/standalone/tower/docs/public → public}/logo.png +0 -0
  423. /package/{.next/standalone/tower/public → public}/web-app-manifest-192x192.png +0 -0
  424. /package/{.next/standalone/tower/public → public}/web-app-manifest-512x512.png +0 -0
  425. /package/{.next/standalone/tower/scripts → scripts}/init-tower.ts +0 -0
  426. /package/{.next/standalone/tower/scripts → scripts}/post-tool-hook.js +0 -0
  427. /package/{.next/standalone/tower/scripts → scripts}/session-start-hook.js +0 -0
  428. /package/{.next/standalone/tower/scripts → scripts}/stop-hook.js +0 -0
  429. /package/{.next/standalone/tower/skills → skills}/tower/SKILL.md +0 -0
  430. /package/{.next/standalone/tower/src → src}/actions/agent-actions.ts +0 -0
  431. /package/{.next/standalone/tower/src → src}/actions/agent-config-actions.ts +0 -0
  432. /package/{.next/standalone/tower/src → src}/actions/ai-config-actions.ts +0 -0
  433. /package/{.next/standalone/tower/src → src}/actions/asset-actions.ts +0 -0
  434. /package/{.next/standalone/tower/src → src}/actions/assistant-actions.ts +0 -0
  435. /package/{.next/standalone/tower/src → src}/actions/cli-profile-actions.ts +0 -0
  436. /package/{.next/standalone/tower/src → src}/actions/config-actions.ts +0 -0
  437. /package/{.next/standalone/tower/src → src}/actions/file-actions.ts +0 -0
  438. /package/{.next/standalone/tower/src → src}/actions/git-actions.ts +0 -0
  439. /package/{.next/standalone/tower/src → src}/actions/label-actions.ts +0 -0
  440. /package/{.next/standalone/tower/src → src}/actions/note-actions.ts +0 -0
  441. /package/{.next/standalone/tower/src → src}/actions/onboarding-actions.ts +0 -0
  442. /package/{.next/standalone/tower/src → src}/actions/preview-actions.ts +0 -0
  443. /package/{.next/standalone/tower/src → src}/actions/project-actions.ts +0 -0
  444. /package/{.next/standalone/tower/src → src}/actions/prompt-actions.ts +0 -0
  445. /package/{.next/standalone/tower/src → src}/actions/report-actions.ts +0 -0
  446. /package/{.next/standalone/tower/src → src}/actions/search-actions.ts +0 -0
  447. /package/{.next/standalone/tower/src → src}/actions/search-code-actions.ts +0 -0
  448. /package/{.next/standalone/tower/src → src}/actions/task-actions.ts +0 -0
  449. /package/{.next/standalone/tower/src → src}/actions/workspace-actions.ts +0 -0
  450. /package/{.next/standalone/tower/src → src}/app/api/adapters/test/route.ts +0 -0
  451. /package/{.next/standalone/tower/src → src}/app/api/browse-fs/route.ts +0 -0
  452. /package/{.next/standalone/tower/src → src}/app/api/files/assets/[projectId]/[filename]/route.ts +0 -0
  453. /package/{.next/standalone/tower/src → src}/app/api/git/route.ts +0 -0
  454. /package/{.next/standalone/tower/src → src}/app/api/internal/assets/[projectId]/[filename]/route.ts +0 -0
  455. /package/{.next/standalone/tower/src → src}/app/api/internal/assets/reveal/route.ts +0 -0
  456. /package/{.next/standalone/tower/src → src}/app/api/internal/assistant/chat/route.ts +0 -0
  457. /package/{.next/standalone/tower/src → src}/app/api/internal/assistant/images/route.ts +0 -0
  458. /package/{.next/standalone/tower/src → src}/app/api/internal/assistant/route.ts +0 -0
  459. /package/{.next/standalone/tower/src → src}/app/api/internal/assistant/sessions/route.ts +0 -0
  460. /package/{.next/standalone/tower/src → src}/app/api/internal/cache/[...segments]/route.ts +0 -0
  461. /package/{.next/standalone/tower/src → src}/app/api/internal/hooks/install/route.ts +0 -0
  462. /package/{.next/standalone/tower/src → src}/app/api/internal/hooks/session/route.ts +0 -0
  463. /package/{.next/standalone/tower/src → src}/app/api/internal/hooks/stop/route.ts +0 -0
  464. /package/{.next/standalone/tower/src → src}/app/api/internal/hooks/upload/route.ts +0 -0
  465. /package/{.next/standalone/tower/src → src}/app/api/internal/notifications/pending/route.ts +0 -0
  466. /package/{.next/standalone/tower/src → src}/app/api/internal/terminal/[taskId]/buffer/route.ts +0 -0
  467. /package/{.next/standalone/tower/src → src}/app/api/internal/terminal/[taskId]/input/route.ts +0 -0
  468. /package/{.next/standalone/tower/src → src}/app/api/internal/terminal/[taskId]/start/route.ts +0 -0
  469. /package/{.next/standalone/tower/src → src}/app/api/tasks/[taskId]/diff/route.ts +0 -0
  470. /package/{.next/standalone/tower/src → src}/app/api/tasks/[taskId]/merge/route.ts +0 -0
  471. /package/{.next/standalone/tower/src → src}/app/apple-icon.png +0 -0
  472. /package/{.next/standalone/tower/docs/public → src/app}/favicon.ico +0 -0
  473. /package/{.next/standalone/tower/src → src}/app/globals.css +0 -0
  474. /package/{.next/standalone/tower/src → src}/app/icon0.svg +0 -0
  475. /package/{.next/standalone/tower/src → src}/app/icon1.png +0 -0
  476. /package/{.next/standalone/tower/src → src}/app/layout.tsx +0 -0
  477. /package/{.next/standalone/tower/src → src}/app/manifest.json +0 -0
  478. /package/{.next/standalone/tower/src → src}/app/missions/missions-client.tsx +0 -0
  479. /package/{.next/standalone/tower/src → src}/app/missions/page.tsx +0 -0
  480. /package/{.next/standalone/tower/src → src}/app/onboarding/page.tsx +0 -0
  481. /package/{.next/standalone/tower/src → src}/app/page.tsx +0 -0
  482. /package/{.next/standalone/tower/src → src}/app/settings/page.tsx +0 -0
  483. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/archive/archive-page-client.tsx +0 -0
  484. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/archive/page.tsx +0 -0
  485. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/assets/assets-page-client.tsx +0 -0
  486. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/assets/page.tsx +0 -0
  487. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/board-page-client.tsx +0 -0
  488. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/notes/notes-page-client.tsx +0 -0
  489. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/notes/page.tsx +0 -0
  490. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/page.tsx +0 -0
  491. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/projects/[projectId]/page.tsx +0 -0
  492. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/tasks/[taskId]/page.tsx +0 -0
  493. /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/tasks/[taskId]/task-page-client.tsx +0 -0
  494. /package/{.next/standalone/tower/src → src}/app/workspaces/page.tsx +0 -0
  495. /package/{.next/standalone/tower/src → src}/components/assets/asset-item.tsx +0 -0
  496. /package/{.next/standalone/tower/src → src}/components/assets/asset-list.tsx +0 -0
  497. /package/{.next/standalone/tower/src → src}/components/assets/asset-upload.tsx +0 -0
  498. /package/{.next/standalone/tower/src → src}/components/assets/image-lightbox.tsx +0 -0
  499. /package/{.next/standalone/tower/src → src}/components/assets/text-preview-dialog.tsx +0 -0
  500. /package/{.next/standalone/tower/src → src}/components/assistant/assistant-chat-bubble.tsx +0 -0
  501. /package/{.next/standalone/tower/src → src}/components/assistant/assistant-chat.tsx +0 -0
  502. /package/{.next/standalone/tower/src → src}/components/assistant/assistant-panel.tsx +0 -0
  503. /package/{.next/standalone/tower/src → src}/components/assistant/assistant-provider.tsx +0 -0
  504. /package/{.next/standalone/tower/src → src}/components/assistant/image-preview-modal.tsx +0 -0
  505. /package/{.next/standalone/tower/src → src}/components/assistant/image-thumbnail-strip.tsx +0 -0
  506. /package/{.next/standalone/tower/src → src}/components/board/board-column.tsx +0 -0
  507. /package/{.next/standalone/tower/src → src}/components/board/board-filters.tsx +0 -0
  508. /package/{.next/standalone/tower/src → src}/components/board/board-stats.tsx +0 -0
  509. /package/{.next/standalone/tower/src → src}/components/board/column-tasks-dialog.tsx +0 -0
  510. /package/{.next/standalone/tower/src → src}/components/board/create-task-dialog.tsx +0 -0
  511. /package/{.next/standalone/tower/src → src}/components/board/kanban-board.tsx +0 -0
  512. /package/{.next/standalone/tower/src → src}/components/board/project-tabs.tsx +0 -0
  513. /package/{.next/standalone/tower/src → src}/components/board/task-card-context-menu.tsx +0 -0
  514. /package/{.next/standalone/tower/src → src}/components/board/task-card.tsx +0 -0
  515. /package/{.next/standalone/tower/src → src}/components/layout/app-sidebar.tsx +0 -0
  516. /package/{.next/standalone/tower/src → src}/components/layout/folder-browser-dialog.tsx +0 -0
  517. /package/{.next/standalone/tower/src → src}/components/layout/layout-client.tsx +0 -0
  518. /package/{.next/standalone/tower/src → src}/components/layout/search-dialog.tsx +0 -0
  519. /package/{.next/standalone/tower/src → src}/components/layout/sub-page-nav.tsx +0 -0
  520. /package/{.next/standalone/tower/src → src}/components/layout/top-bar.tsx +0 -0
  521. /package/{.next/standalone/tower/src → src}/components/missions/grid-layout-presets.ts +0 -0
  522. /package/{.next/standalone/tower/src → src}/components/missions/grid-preset-picker.tsx +0 -0
  523. /package/{.next/standalone/tower/src → src}/components/missions/merge-missions.ts +0 -0
  524. /package/{.next/standalone/tower/src → src}/components/missions/mission-card.tsx +0 -0
  525. /package/{.next/standalone/tower/src → src}/components/missions/task-picker-dialog.tsx +0 -0
  526. /package/{.next/standalone/tower/src → src}/components/notes/category-filter.tsx +0 -0
  527. /package/{.next/standalone/tower/src → src}/components/notes/note-card.tsx +0 -0
  528. /package/{.next/standalone/tower/src → src}/components/notes/note-editor.tsx +0 -0
  529. /package/{.next/standalone/tower/src → src}/components/notes/note-list.tsx +0 -0
  530. /package/{.next/standalone/tower/src → src}/components/notifications/notification-permission-banner.tsx +0 -0
  531. /package/{.next/standalone/tower/src → src}/components/notifications/use-notification-listener.ts +0 -0
  532. /package/{.next/standalone/tower/src → src}/components/onboarding/guided-tour.tsx +0 -0
  533. /package/{.next/standalone/tower/src → src}/components/onboarding/onboarding-wizard.tsx +0 -0
  534. /package/{.next/standalone/tower/src → src}/components/onboarding/wizard-step-cli.tsx +0 -0
  535. /package/{.next/standalone/tower/src → src}/components/onboarding/wizard-step-username.tsx +0 -0
  536. /package/{.next/standalone/tower/src → src}/components/project/create-project-dialog.tsx +0 -0
  537. /package/{.next/standalone/tower/src → src}/components/project/import-project-dialog.tsx +0 -0
  538. /package/{.next/standalone/tower/src → src}/components/providers/theme-provider.tsx +0 -0
  539. /package/{.next/standalone/tower/src → src}/components/repository/create-branch-dialog.tsx +0 -0
  540. /package/{.next/standalone/tower/src → src}/components/repository/git-changes-panel.tsx +0 -0
  541. /package/{.next/standalone/tower/src → src}/components/repository/git-log-panel.tsx +0 -0
  542. /package/{.next/standalone/tower/src → src}/components/repository/git-stash-panel.tsx +0 -0
  543. /package/{.next/standalone/tower/src → src}/components/repository/repo-sidebar.tsx +0 -0
  544. /package/{.next/standalone/tower/src → src}/components/settings/cli-adapter-tester.tsx +0 -0
  545. /package/{.next/standalone/tower/src → src}/components/settings/settings-page.tsx +0 -0
  546. /package/{.next/standalone/tower/src → src}/components/task/code-editor.tsx +0 -0
  547. /package/{.next/standalone/tower/src → src}/components/task/code-search.tsx +0 -0
  548. /package/{.next/standalone/tower/src → src}/components/task/diff-editor.tsx +0 -0
  549. /package/{.next/standalone/tower/src → src}/components/task/editor-git-panel.tsx +0 -0
  550. /package/{.next/standalone/tower/src → src}/components/task/editor-tabs.tsx +0 -0
  551. /package/{.next/standalone/tower/src → src}/components/task/execution-timeline.tsx +0 -0
  552. /package/{.next/standalone/tower/src → src}/components/task/file-tree-context-menu.tsx +0 -0
  553. /package/{.next/standalone/tower/src → src}/components/task/file-tree-node.tsx +0 -0
  554. /package/{.next/standalone/tower/src → src}/components/task/file-tree.tsx +0 -0
  555. /package/{.next/standalone/tower/src → src}/components/task/preview-panel.tsx +0 -0
  556. /package/{.next/standalone/tower/src → src}/components/task/task-detail-panel.tsx +0 -0
  557. /package/{.next/standalone/tower/src → src}/components/task/task-diff-view.tsx +0 -0
  558. /package/{.next/standalone/tower/src → src}/components/task/task-file-changes.tsx +0 -0
  559. /package/{.next/standalone/tower/src → src}/components/task/task-merge-confirm-dialog.tsx +0 -0
  560. /package/{.next/standalone/tower/src → src}/components/task/task-metadata.tsx +0 -0
  561. /package/{.next/standalone/tower/src → src}/components/task/task-notes-panel.tsx +0 -0
  562. /package/{.next/standalone/tower/src → src}/components/task/task-overview-drawer.tsx +0 -0
  563. /package/{.next/standalone/tower/src → src}/components/task/task-terminal.tsx +0 -0
  564. /package/{.next/standalone/tower/src → src}/components/task/terminal-portal.tsx +0 -0
  565. /package/{.next/standalone/tower/src → src}/components/task/types.ts +0 -0
  566. /package/{.next/standalone/tower/src → src}/components/ui/avatar.tsx +0 -0
  567. /package/{.next/standalone/tower/src → src}/components/ui/badge.tsx +0 -0
  568. /package/{.next/standalone/tower/src → src}/components/ui/button.tsx +0 -0
  569. /package/{.next/standalone/tower/src → src}/components/ui/card.tsx +0 -0
  570. /package/{.next/standalone/tower/src → src}/components/ui/command.tsx +0 -0
  571. /package/{.next/standalone/tower/src → src}/components/ui/dialog.tsx +0 -0
  572. /package/{.next/standalone/tower/src → src}/components/ui/dropdown-menu.tsx +0 -0
  573. /package/{.next/standalone/tower/src → src}/components/ui/empty-state.tsx +0 -0
  574. /package/{.next/standalone/tower/src → src}/components/ui/error-boundary.tsx +0 -0
  575. /package/{.next/standalone/tower/src → src}/components/ui/input-group.tsx +0 -0
  576. /package/{.next/standalone/tower/src → src}/components/ui/input.tsx +0 -0
  577. /package/{.next/standalone/tower/src → src}/components/ui/label.tsx +0 -0
  578. /package/{.next/standalone/tower/src → src}/components/ui/popover.tsx +0 -0
  579. /package/{.next/standalone/tower/src → src}/components/ui/scroll-area.tsx +0 -0
  580. /package/{.next/standalone/tower/src → src}/components/ui/segmented-control.tsx +0 -0
  581. /package/{.next/standalone/tower/src → src}/components/ui/select.tsx +0 -0
  582. /package/{.next/standalone/tower/src → src}/components/ui/separator.tsx +0 -0
  583. /package/{.next/standalone/tower/src → src}/components/ui/sheet.tsx +0 -0
  584. /package/{.next/standalone/tower/src → src}/components/ui/sonner.tsx +0 -0
  585. /package/{.next/standalone/tower/src → src}/components/ui/switch.tsx +0 -0
  586. /package/{.next/standalone/tower/src → src}/components/ui/tabs.tsx +0 -0
  587. /package/{.next/standalone/tower/src → src}/components/ui/textarea.tsx +0 -0
  588. /package/{.next/standalone/tower/src → src}/components/ui/toast.tsx +0 -0
  589. /package/{.next/standalone/tower/src → src}/components/ui/tooltip.tsx +0 -0
  590. /package/{.next/standalone/tower/src → src}/hooks/sse-event-reducer.ts +0 -0
  591. /package/{.next/standalone/tower/src → src}/hooks/use-assistant-chat.ts +0 -0
  592. /package/{.next/standalone/tower/src → src}/hooks/use-image-upload.ts +0 -0
  593. /package/{.next/standalone/tower/src → src}/instrumentation.ts +0 -0
  594. /package/{.next/standalone/tower/src → src}/lib/ai/adapters/cli/claude-cli-adapter.ts +0 -0
  595. /package/{.next/standalone/tower/src → src}/lib/ai/capability-resolver.ts +0 -0
  596. /package/{.next/standalone/tower/src → src}/lib/ai/provider-registry.ts +0 -0
  597. /package/{.next/standalone/tower/src → src}/lib/ai/providers/claude.ts +0 -0
  598. /package/{.next/standalone/tower/src → src}/lib/ai/providers/index.ts +0 -0
  599. /package/{.next/standalone/tower/src → src}/lib/ai/types.ts +0 -0
  600. /package/{.next/standalone/tower/src → src}/lib/assistant-constants.ts +0 -0
  601. /package/{.next/standalone/tower/src → src}/lib/assistant-message-converter.ts +0 -0
  602. /package/{.next/standalone/tower/src → src}/lib/assistant-sessions.ts +0 -0
  603. /package/{.next/standalone/tower/src → src}/lib/build-multimodal-prompt.ts +0 -0
  604. /package/{.next/standalone/tower/src → src}/lib/claude-session.ts +0 -0
  605. /package/{.next/standalone/tower/src → src}/lib/cli-test.ts +0 -0
  606. /package/{.next/standalone/tower/src → src}/lib/config-defaults.ts +0 -0
  607. /package/{.next/standalone/tower/src → src}/lib/config-reader.ts +0 -0
  608. /package/{.next/standalone/tower/src → src}/lib/constants.ts +0 -0
  609. /package/{.next/standalone/tower/src → src}/lib/db.ts +0 -0
  610. /package/{.next/standalone/tower/src → src}/lib/diff-parser.ts +0 -0
  611. /package/{.next/standalone/tower/src → src}/lib/execution-summary.ts +0 -0
  612. /package/{.next/standalone/tower/src → src}/lib/file-serve-client.ts +0 -0
  613. /package/{.next/standalone/tower/src → src}/lib/file-serve.ts +0 -0
  614. /package/{.next/standalone/tower/src → src}/lib/file-utils.ts +0 -0
  615. /package/{.next/standalone/tower/src → src}/lib/fs-security.ts +0 -0
  616. /package/{.next/standalone/tower/src → src}/lib/fts.ts +0 -0
  617. /package/{.next/standalone/tower/src → src}/lib/git-api.ts +0 -0
  618. /package/{.next/standalone/tower/src → src}/lib/git-url.ts +0 -0
  619. /package/{.next/standalone/tower/src → src}/lib/i18n/en.ts +0 -0
  620. /package/{.next/standalone/tower/src → src}/lib/i18n/types.ts +0 -0
  621. /package/{.next/standalone/tower/src → src}/lib/i18n/zh.ts +0 -0
  622. /package/{.next/standalone/tower/src → src}/lib/i18n.tsx +0 -0
  623. /package/{.next/standalone/tower/src → src}/lib/init-tower.ts +0 -0
  624. /package/{.next/standalone/tower/src → src}/lib/instrumentation-tasks.ts +0 -0
  625. /package/{.next/standalone/tower/src → src}/lib/internal-api-guard.ts +0 -0
  626. /package/{.next/standalone/tower/src → src}/lib/logger.ts +0 -0
  627. /package/{.next/standalone/tower/src → src}/lib/mime-magic.ts +0 -0
  628. /package/{.next/standalone/tower/src → src}/lib/platform.ts +0 -0
  629. /package/{.next/standalone/tower/src → src}/lib/preview-process.ts +0 -0
  630. /package/{.next/standalone/tower/src → src}/lib/pty/pty-session.ts +0 -0
  631. /package/{.next/standalone/tower/src → src}/lib/pty/session-store.ts +0 -0
  632. /package/{.next/standalone/tower/src → src}/lib/pty/ws-server.ts +0 -0
  633. /package/{.next/standalone/tower/src → src}/lib/schemas.ts +0 -0
  634. /package/{.next/standalone/tower/src → src}/lib/search.ts +0 -0
  635. /package/{.next/standalone/tower/src → src}/lib/terminal-link-provider.ts +0 -0
  636. /package/{.next/standalone/tower/src → src}/lib/tower-dir.ts +0 -0
  637. /package/{.next/standalone/tower/src → src}/lib/utils.ts +0 -0
  638. /package/{.next/standalone/tower/src → src}/lib/worktree.ts +0 -0
  639. /package/{.next/standalone/tower/src → src}/stores/board-store.ts +0 -0
  640. /package/{.next/standalone/tower/src → src}/stores/task-execution-store.ts +0 -0
  641. /package/{.next/standalone/tower/src → src}/types/index.ts +0 -0
@@ -1,1345 +0,0 @@
1
- # CLI 抽象层 Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Extract Claude Code CLI hardcoded logic into a provider-agnostic adapter layer, preserving all existing functionality.
6
-
7
- **Architecture:** Strategy pattern for CLI adapters (PTY execution) + generic adapters for AI queries. A ProviderRegistry maps provider names to adapters. A capability-resolver reads DB config to route each AI slot to the correct adapter.
8
-
9
- **Tech Stack:** TypeScript, Prisma (SQLite), Next.js 16 server actions, node-pty, @anthropic-ai/claude-agent-sdk
10
-
11
- **Spec:** `docs/ai/cli-abstraction-design.md`
12
-
13
- ---
14
-
15
- ### Task 1: Define types and error classes
16
-
17
- **Files:**
18
- - Create: `src/lib/ai/types.ts`
19
-
20
- - [ ] **Step 1: Create the types file with all interfaces**
21
-
22
- ```typescript
23
- // src/lib/ai/types.ts
24
-
25
- // ---------------------------------------------------------------------------
26
- // CLI Adapter — PTY execution layer
27
- // ---------------------------------------------------------------------------
28
-
29
- export interface CliSpawnOptions {
30
- taskId: string;
31
- prompt: string;
32
- cwd: string;
33
- envOverrides?: Record<string, string>;
34
- resumeSessionId?: string;
35
- continueLatest?: boolean;
36
- worktreePath?: string;
37
- profileArgs?: string[];
38
- profileEnvVars?: Record<string, string>;
39
- }
40
-
41
- export interface CliSpawnResult {
42
- command: string;
43
- args: string[];
44
- env: Record<string, string>;
45
- /** Gemini: spawn 后通过 PTY write 发送初始 prompt */
46
- initialInput?: string;
47
- }
48
-
49
- export interface CliAdapter {
50
- buildSpawnArgs(opts: CliSpawnOptions): CliSpawnResult;
51
-
52
- buildEnvOverrides(opts: {
53
- taskId: string;
54
- taskTitle: string;
55
- apiUrl: string;
56
- callbackUrl?: string;
57
- }): Record<string, string>;
58
-
59
- installHooks(apiUrl: string): Promise<void>;
60
- uninstallHooks(): Promise<void>;
61
- isHooksInstalled(): Promise<boolean>;
62
-
63
- isAvailable(): Promise<boolean>;
64
- getVersion(): Promise<string | null>;
65
- getModels(): Promise<string[]>;
66
-
67
- getConfigDir(): string;
68
- getSettingsPath(): string;
69
- getSessionsDir(): string;
70
- }
71
-
72
- // ---------------------------------------------------------------------------
73
- // AI Query Adapter — single-turn and streaming queries
74
- // ---------------------------------------------------------------------------
75
-
76
- export interface AiQueryOptions {
77
- prompt: string;
78
- cwd?: string;
79
- systemPrompt?: string;
80
- model?: string;
81
- maxTurns?: number;
82
- tools?: string[];
83
- allowedTools?: string[];
84
- }
85
-
86
- export interface AiQueryResult {
87
- content: string | null;
88
- usage?: { inputTokens: number; outputTokens: number };
89
- }
90
-
91
- export interface AiQueryChunk {
92
- type: "text" | "tool_use" | "tool_result" | "error";
93
- content: string;
94
- }
95
-
96
- export interface AiQueryAdapter {
97
- query(opts: AiQueryOptions): Promise<AiQueryResult>;
98
- queryStream?(opts: AiQueryOptions): AsyncIterable<AiQueryChunk>;
99
-
100
- isAvailable(): Promise<boolean>;
101
- getModels(): Promise<string[]>;
102
- }
103
-
104
- // ---------------------------------------------------------------------------
105
- // Provider definition & registry types
106
- // ---------------------------------------------------------------------------
107
-
108
- export interface ProviderDefinition {
109
- name: string;
110
- displayName: string;
111
- agentFieldValue: string;
112
-
113
- cli?: {
114
- command: string;
115
- adapter: CliAdapter;
116
- };
117
- api?: {
118
- keyEnvVar: string;
119
- adapter: AiQueryAdapter;
120
- };
121
- cliQuery?: {
122
- adapter: AiQueryAdapter;
123
- };
124
-
125
- models: {
126
- cli: string[];
127
- api: string[];
128
- };
129
- }
130
-
131
- export interface ProviderAvailability {
132
- name: string;
133
- displayName: string;
134
- cli: { available: boolean; version: string | null };
135
- api: { available: boolean; keyConfigured: boolean };
136
- }
137
-
138
- // ---------------------------------------------------------------------------
139
- // Capability slot config (mirrors Prisma model)
140
- // ---------------------------------------------------------------------------
141
-
142
- export type AiSlot = "terminal" | "summary" | "dreaming" | "analysis" | "assistant";
143
-
144
- export interface AiSlotConfig {
145
- slot: AiSlot;
146
- provider: string;
147
- mode: "cli" | "api";
148
- model?: string;
149
- }
150
-
151
- // ---------------------------------------------------------------------------
152
- // Errors
153
- // ---------------------------------------------------------------------------
154
-
155
- export type AiProviderErrorCode =
156
- | "CLI_NOT_FOUND"
157
- | "API_KEY_MISSING"
158
- | "MODEL_NOT_AVAILABLE"
159
- | "RATE_LIMITED"
160
- | "NETWORK_ERROR"
161
- | "UNSUPPORTED_MODE"
162
- | "SPAWN_FAILED";
163
-
164
- export class AiProviderError extends Error {
165
- constructor(
166
- public code: AiProviderErrorCode,
167
- public provider: string,
168
- message: string,
169
- ) {
170
- super(message);
171
- this.name = "AiProviderError";
172
- }
173
- }
174
- ```
175
-
176
- - [ ] **Step 2: Verify TypeScript compiles**
177
-
178
- Run: `cd /Users/liujunping/project/f/tower && npx tsc --noEmit src/lib/ai/types.ts 2>&1 | head -20`
179
- Expected: No errors (file is self-contained, no imports needed)
180
-
181
- - [ ] **Step 3: Commit**
182
-
183
- ```bash
184
- git add src/lib/ai/types.ts
185
- git commit -m "feat(ai): define CLI adapter and AI query adapter interfaces"
186
- ```
187
-
188
- ---
189
-
190
- ### Task 2: Implement ClaudeCliAdapter
191
-
192
- **Files:**
193
- - Create: `src/lib/ai/adapters/cli/claude-cli-adapter.ts`
194
- - Reference: `src/actions/agent-actions.ts:194-236` (resume args), `src/actions/agent-actions.ts:598-620` (env + args), `src/app/api/internal/hooks/install/route.ts` (hook install)
195
-
196
- - [ ] **Step 1: Write the failing test**
197
-
198
- Create: `src/lib/ai/__tests__/claude-cli-adapter.test.ts`
199
-
200
- ```typescript
201
- import { describe, it, expect, vi, beforeEach } from "vitest";
202
- import { ClaudeCliAdapter } from "../adapters/cli/claude-cli-adapter";
203
- import type { CliSpawnOptions } from "../types";
204
-
205
- describe("ClaudeCliAdapter", () => {
206
- let adapter: ClaudeCliAdapter;
207
-
208
- beforeEach(() => {
209
- adapter = new ClaudeCliAdapter();
210
- });
211
-
212
- describe("buildSpawnArgs", () => {
213
- const baseOpts: CliSpawnOptions = {
214
- taskId: "ctask123456789012345678",
215
- prompt: "Fix the bug",
216
- cwd: "/project",
217
- };
218
-
219
- it("builds fresh start args with prompt as last argument", () => {
220
- const result = adapter.buildSpawnArgs(baseOpts);
221
- expect(result.command).toBe("claude");
222
- expect(result.args[result.args.length - 1]).toBe("Fix the bug");
223
- expect(result.initialInput).toBeUndefined();
224
- });
225
-
226
- it("builds resume args with --resume flag", () => {
227
- const result = adapter.buildSpawnArgs({
228
- ...baseOpts,
229
- resumeSessionId: "session-abc-123",
230
- });
231
- expect(result.args).toContain("--resume");
232
- expect(result.args).toContain("session-abc-123");
233
- // resume mode should NOT include prompt as argument
234
- expect(result.args[result.args.length - 1]).not.toBe("Fix the bug");
235
- });
236
-
237
- it("builds continue args with --continue flag and no prompt", () => {
238
- const result = adapter.buildSpawnArgs({
239
- ...baseOpts,
240
- continueLatest: true,
241
- });
242
- expect(result.args).toContain("--continue");
243
- // continue mode should NOT include prompt as argument
244
- expect(result.args).not.toContain("Fix the bug");
245
- });
246
-
247
- it("merges profileArgs into args", () => {
248
- const result = adapter.buildSpawnArgs({
249
- ...baseOpts,
250
- profileArgs: ["--model", "opus"],
251
- });
252
- expect(result.args).toContain("--model");
253
- expect(result.args).toContain("opus");
254
- });
255
-
256
- it("merges profileEnvVars into env", () => {
257
- const result = adapter.buildSpawnArgs({
258
- ...baseOpts,
259
- profileEnvVars: { CUSTOM_VAR: "value" },
260
- });
261
- expect(result.env.CUSTOM_VAR).toBe("value");
262
- });
263
- });
264
-
265
- describe("buildEnvOverrides", () => {
266
- it("returns TOWER_* env vars", () => {
267
- const env = adapter.buildEnvOverrides({
268
- taskId: "ctask123",
269
- taskTitle: "Test task",
270
- apiUrl: "http://localhost:3000",
271
- });
272
- expect(env.TOWER_TASK_ID).toBe("ctask123");
273
- expect(env.TOWER_TASK_TITLE).toBe("Test task");
274
- expect(env.TOWER_API_URL).toBe("http://localhost:3000");
275
- expect(env.TOWER_STARTED_AT).toBeDefined();
276
- });
277
-
278
- it("includes CALLBACK_URL when provided", () => {
279
- const env = adapter.buildEnvOverrides({
280
- taskId: "ctask123",
281
- taskTitle: "Test",
282
- apiUrl: "http://localhost:3000",
283
- callbackUrl: "http://external/callback",
284
- });
285
- expect(env.CALLBACK_URL).toBe("http://external/callback");
286
- });
287
-
288
- it("omits CALLBACK_URL when not provided", () => {
289
- const env = adapter.buildEnvOverrides({
290
- taskId: "ctask123",
291
- taskTitle: "Test",
292
- apiUrl: "http://localhost:3000",
293
- });
294
- expect(env.CALLBACK_URL).toBeUndefined();
295
- });
296
- });
297
-
298
- describe("metadata", () => {
299
- it("returns correct config paths", () => {
300
- expect(adapter.getConfigDir()).toContain(".claude");
301
- expect(adapter.getSettingsPath()).toContain("settings.json");
302
- expect(adapter.getSessionsDir()).toContain("projects");
303
- });
304
- });
305
- });
306
- ```
307
-
308
- - [ ] **Step 2: Run test to verify it fails**
309
-
310
- Run: `cd /Users/liujunping/project/f/tower && pnpm vitest run src/lib/ai/__tests__/claude-cli-adapter.test.ts 2>&1 | tail -20`
311
- Expected: FAIL — module not found
312
-
313
- - [ ] **Step 3: Implement ClaudeCliAdapter**
314
-
315
- ```typescript
316
- // src/lib/ai/adapters/cli/claude-cli-adapter.ts
317
-
318
- import * as fs from "node:fs";
319
- import * as os from "node:os";
320
- import * as path from "node:path";
321
- import { resolveCommandPathSync } from "@/lib/platform";
322
- import type { CliAdapter, CliSpawnOptions, CliSpawnResult } from "../../types";
323
-
324
- /** Known Claude Code model aliases (static — CLI has no list command) */
325
- const CLAUDE_MODELS = ["sonnet", "opus", "haiku", "claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5-20251001"];
326
-
327
- export class ClaudeCliAdapter implements CliAdapter {
328
-
329
- // -- spawn args ----------------------------------------------------------
330
-
331
- buildSpawnArgs(opts: CliSpawnOptions): CliSpawnResult {
332
- const args: string[] = [];
333
-
334
- // Profile args first (e.g. --model, --dangerously-skip-permissions)
335
- if (opts.profileArgs?.length) {
336
- args.push(...opts.profileArgs);
337
- }
338
-
339
- // Mode: resume / continue / fresh
340
- if (opts.resumeSessionId) {
341
- args.push("--resume", opts.resumeSessionId);
342
- } else if (opts.continueLatest) {
343
- args.push("--continue");
344
- } else {
345
- // Fresh start: prompt as last argument
346
- if (opts.prompt) {
347
- args.push(opts.prompt);
348
- }
349
- }
350
-
351
- // Env: merge profile vars (adapter env built separately via buildEnvOverrides)
352
- const env: Record<string, string> = {
353
- ...(opts.profileEnvVars ?? {}),
354
- ...(opts.envOverrides ?? {}),
355
- };
356
-
357
- return {
358
- command: this.resolveCommand(),
359
- args,
360
- env,
361
- };
362
- }
363
-
364
- // -- env overrides -------------------------------------------------------
365
-
366
- buildEnvOverrides(opts: {
367
- taskId: string;
368
- taskTitle: string;
369
- apiUrl: string;
370
- callbackUrl?: string;
371
- }): Record<string, string> {
372
- const env: Record<string, string> = {
373
- TOWER_TASK_ID: opts.taskId,
374
- TOWER_TASK_TITLE: opts.taskTitle,
375
- TOWER_STARTED_AT: new Date().toISOString(),
376
- TOWER_API_URL: opts.apiUrl,
377
- };
378
- if (opts.callbackUrl) {
379
- env.CALLBACK_URL = opts.callbackUrl;
380
- }
381
- return env;
382
- }
383
-
384
- // -- hooks ---------------------------------------------------------------
385
-
386
- async installHooks(apiUrl: string): Promise<void> {
387
- const settings = this.readSettings();
388
- const hooks = (settings["hooks"] as Record<string, unknown>) ?? {};
389
- const entries = this.getPostToolUseArray(settings);
390
-
391
- if (this.findTowerHookIndex(entries) >= 0) return; // already installed
392
-
393
- const hookPath = path.join(process.cwd(), "scripts", "post-tool-hook.js");
394
- const newEntry = {
395
- hooks: [{ command: `node "${hookPath}"`, timeout: 10, type: "command" }],
396
- matcher: "Write|Edit|MultiEdit",
397
- };
398
-
399
- settings["hooks"] = { ...hooks, PostToolUse: [...entries, newEntry] };
400
- this.writeSettings(settings);
401
- }
402
-
403
- async uninstallHooks(): Promise<void> {
404
- const settings = this.readSettings();
405
- const hooks = (settings["hooks"] as Record<string, unknown>) ?? {};
406
- const entries = this.getPostToolUseArray(settings);
407
- const filtered = entries.filter(
408
- (e) => !e.hooks?.some((h: { command?: string }) => h.command?.includes("post-tool-hook.js"))
409
- );
410
- settings["hooks"] = { ...hooks, PostToolUse: filtered };
411
- this.writeSettings(settings);
412
- }
413
-
414
- async isHooksInstalled(): Promise<boolean> {
415
- const settings = this.readSettings();
416
- const entries = this.getPostToolUseArray(settings);
417
- return this.findTowerHookIndex(entries) >= 0;
418
- }
419
-
420
- // -- capability detection ------------------------------------------------
421
-
422
- async isAvailable(): Promise<boolean> {
423
- const version = await this.getVersion();
424
- return version !== null;
425
- }
426
-
427
- async getVersion(): Promise<string | null> {
428
- try {
429
- const { execFile } = await import("child_process");
430
- const { promisify } = await import("util");
431
- const execFileAsync = promisify(execFile);
432
- const cmd = this.resolveCommand();
433
- const { stdout } = await execFileAsync(cmd, ["--version"], { timeout: 5000 });
434
- return stdout.trim() || null;
435
- } catch {
436
- return null;
437
- }
438
- }
439
-
440
- async getModels(): Promise<string[]> {
441
- return CLAUDE_MODELS;
442
- }
443
-
444
- // -- paths ---------------------------------------------------------------
445
-
446
- getConfigDir(): string {
447
- return path.join(os.homedir(), ".claude");
448
- }
449
-
450
- getSettingsPath(): string {
451
- return path.join(this.getConfigDir(), "settings.json");
452
- }
453
-
454
- getSessionsDir(): string {
455
- return path.join(this.getConfigDir(), "projects");
456
- }
457
-
458
- // -- private helpers -----------------------------------------------------
459
-
460
- /** Resolve claude binary — env var > platform-aware resolution.
461
- * Consolidates duplicate findClaudeBinary() from claude-session.ts + assistant route. */
462
- /** Resolve claude binary — env var > platform-aware resolution.
463
- * Public so claude-session.ts can reuse instead of duplicating findClaudeBinary(). */
464
- resolveCommand(): string {
465
- if (process.env.CLAUDE_CODE_PATH) return process.env.CLAUDE_CODE_PATH;
466
- if (process.platform === "win32") {
467
- const native = resolveCommandPathSync("claude-code");
468
- if (native !== "claude-code") return native;
469
- }
470
- return resolveCommandPathSync("claude");
471
- }
472
-
473
- private readSettings(): Record<string, unknown> {
474
- try {
475
- return JSON.parse(fs.readFileSync(this.getSettingsPath(), "utf-8")) as Record<string, unknown>;
476
- } catch {
477
- return {};
478
- }
479
- }
480
-
481
- private writeSettings(data: Record<string, unknown>): void {
482
- const dir = this.getConfigDir();
483
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
484
- fs.writeFileSync(this.getSettingsPath(), JSON.stringify(data, null, 2), "utf-8");
485
- }
486
-
487
- private getPostToolUseArray(settings: Record<string, unknown>): Array<{
488
- hooks: Array<{ command: string; timeout: number; type: string }>;
489
- matcher: string;
490
- }> {
491
- const hooks = settings["hooks"] as Record<string, unknown> | undefined;
492
- if (!hooks) return [];
493
- const arr = hooks["PostToolUse"];
494
- return Array.isArray(arr) ? arr : [];
495
- }
496
-
497
- private findTowerHookIndex(entries: Array<{ hooks: Array<{ command?: string }> }>): number {
498
- return entries.findIndex((e) =>
499
- e.hooks?.some((h) => h.command?.includes("post-tool-hook.js"))
500
- );
501
- }
502
- }
503
- ```
504
-
505
- - [ ] **Step 4: Run test to verify it passes**
506
-
507
- Run: `cd /Users/liujunping/project/f/tower && pnpm vitest run src/lib/ai/__tests__/claude-cli-adapter.test.ts 2>&1 | tail -20`
508
- Expected: All tests PASS
509
-
510
- - [ ] **Step 5: Commit**
511
-
512
- ```bash
513
- git add src/lib/ai/adapters/cli/claude-cli-adapter.ts src/lib/ai/__tests__/claude-cli-adapter.test.ts
514
- git commit -m "feat(ai): implement ClaudeCliAdapter with tests"
515
- ```
516
-
517
- ---
518
-
519
- ### Task 3: Implement ProviderRegistry
520
-
521
- **Files:**
522
- - Create: `src/lib/ai/provider-registry.ts`
523
- - Create: `src/lib/ai/providers/claude.ts`
524
- - Create: `src/lib/ai/providers/index.ts`
525
-
526
- - [ ] **Step 1: Write the failing test**
527
-
528
- Create: `src/lib/ai/__tests__/provider-registry.test.ts`
529
-
530
- ```typescript
531
- import { describe, it, expect, beforeEach } from "vitest";
532
- import { ProviderRegistry } from "../provider-registry";
533
- import type { ProviderDefinition } from "../types";
534
- import { ClaudeCliAdapter } from "../adapters/cli/claude-cli-adapter";
535
-
536
- function makeClaudeProvider(): ProviderDefinition {
537
- return {
538
- name: "claude",
539
- displayName: "Claude Code",
540
- agentFieldValue: "CLAUDE_CODE",
541
- cli: {
542
- command: "claude",
543
- adapter: new ClaudeCliAdapter(),
544
- },
545
- models: {
546
- cli: ["sonnet", "opus", "haiku"],
547
- api: [],
548
- },
549
- };
550
- }
551
-
552
- describe("ProviderRegistry", () => {
553
- let registry: ProviderRegistry;
554
-
555
- beforeEach(() => {
556
- registry = new ProviderRegistry();
557
- });
558
-
559
- it("registers and retrieves a provider", () => {
560
- registry.register(makeClaudeProvider());
561
- const provider = registry.get("claude");
562
- expect(provider).toBeDefined();
563
- expect(provider!.displayName).toBe("Claude Code");
564
- });
565
-
566
- it("returns undefined for unknown provider", () => {
567
- expect(registry.get("unknown")).toBeUndefined();
568
- });
569
-
570
- it("returns CLI adapter for registered provider", () => {
571
- registry.register(makeClaudeProvider());
572
- const adapter = registry.getCliAdapter("claude");
573
- expect(adapter).toBeDefined();
574
- });
575
-
576
- it("returns null CLI adapter for provider without CLI", () => {
577
- registry.register({
578
- ...makeClaudeProvider(),
579
- name: "api-only",
580
- cli: undefined,
581
- });
582
- expect(registry.getCliAdapter("api-only")).toBeNull();
583
- });
584
-
585
- it("returns all allowed commands from registered CLI providers", () => {
586
- registry.register(makeClaudeProvider());
587
- expect(registry.getAllowedCommands()).toContain("claude");
588
- });
589
-
590
- it("lists all providers", () => {
591
- registry.register(makeClaudeProvider());
592
- expect(registry.getAll()).toHaveLength(1);
593
- });
594
- });
595
- ```
596
-
597
- - [ ] **Step 2: Run test to verify it fails**
598
-
599
- Run: `cd /Users/liujunping/project/f/tower && pnpm vitest run src/lib/ai/__tests__/provider-registry.test.ts 2>&1 | tail -20`
600
- Expected: FAIL
601
-
602
- - [ ] **Step 3: Implement ProviderRegistry**
603
-
604
- ```typescript
605
- // src/lib/ai/provider-registry.ts
606
-
607
- import type { CliAdapter, AiQueryAdapter, ProviderDefinition, ProviderAvailability } from "./types";
608
-
609
- export class ProviderRegistry {
610
- private providers = new Map<string, ProviderDefinition>();
611
-
612
- register(provider: ProviderDefinition): void {
613
- this.providers.set(provider.name, provider);
614
- }
615
-
616
- get(name: string): ProviderDefinition | undefined {
617
- return this.providers.get(name);
618
- }
619
-
620
- getAll(): ProviderDefinition[] {
621
- return Array.from(this.providers.values());
622
- }
623
-
624
- getCliAdapter(name: string): CliAdapter | null {
625
- return this.providers.get(name)?.cli?.adapter ?? null;
626
- }
627
-
628
- getQueryAdapter(name: string, mode: "api" | "cli"): AiQueryAdapter | null {
629
- const provider = this.providers.get(name);
630
- if (!provider) return null;
631
- if (mode === "api") return provider.api?.adapter ?? null;
632
- return provider.cliQuery?.adapter ?? null;
633
- }
634
-
635
- /** Returns all CLI command names from registered providers (for security allowlist). */
636
- getAllowedCommands(): string[] {
637
- const commands: string[] = [];
638
- for (const p of this.providers.values()) {
639
- if (p.cli?.command) commands.push(p.cli.command);
640
- }
641
- return commands;
642
- }
643
-
644
- async getAvailableProviders(): Promise<ProviderAvailability[]> {
645
- const results: ProviderAvailability[] = [];
646
- for (const p of this.providers.values()) {
647
- const cliAvailable = p.cli ? await p.cli.adapter.isAvailable() : false;
648
- const cliVersion = cliAvailable && p.cli ? await p.cli.adapter.getVersion() : null;
649
- const apiKeyConfigured = p.api ? !!process.env[p.api.keyEnvVar] : false;
650
- const apiAvailable = p.api ? apiKeyConfigured : false;
651
-
652
- results.push({
653
- name: p.name,
654
- displayName: p.displayName,
655
- cli: { available: cliAvailable, version: cliVersion },
656
- api: { available: apiAvailable, keyConfigured: apiKeyConfigured },
657
- });
658
- }
659
- return results;
660
- }
661
- }
662
- ```
663
-
664
- - [ ] **Step 4: Implement Claude provider definition + registration**
665
-
666
- ```typescript
667
- // src/lib/ai/providers/claude.ts
668
-
669
- import type { ProviderDefinition } from "../types";
670
- import { ClaudeCliAdapter } from "../adapters/cli/claude-cli-adapter";
671
-
672
- export function createClaudeProvider(): ProviderDefinition {
673
- return {
674
- name: "claude",
675
- displayName: "Claude Code",
676
- agentFieldValue: "CLAUDE_CODE",
677
- cli: {
678
- command: "claude",
679
- adapter: new ClaudeCliAdapter(),
680
- },
681
- // api and cliQuery adapters will be added in Phase 2
682
- models: {
683
- cli: ["sonnet", "opus", "haiku", "claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5-20251001"],
684
- api: [],
685
- },
686
- };
687
- }
688
- ```
689
-
690
- ```typescript
691
- // src/lib/ai/providers/index.ts
692
-
693
- import { ProviderRegistry } from "../provider-registry";
694
- import { createClaudeProvider } from "./claude";
695
-
696
- // Singleton registry — survives HMR via globalThis
697
- const g = globalThis as typeof globalThis & { __providerRegistry?: ProviderRegistry };
698
- if (!g.__providerRegistry) {
699
- const registry = new ProviderRegistry();
700
- registry.register(createClaudeProvider());
701
- g.__providerRegistry = registry;
702
- }
703
-
704
- export const providerRegistry = g.__providerRegistry;
705
- ```
706
-
707
- - [ ] **Step 5: Run test to verify it passes**
708
-
709
- Run: `cd /Users/liujunping/project/f/tower && pnpm vitest run src/lib/ai/__tests__/provider-registry.test.ts 2>&1 | tail -20`
710
- Expected: All tests PASS
711
-
712
- - [ ] **Step 6: Commit**
713
-
714
- ```bash
715
- git add src/lib/ai/provider-registry.ts src/lib/ai/providers/claude.ts src/lib/ai/providers/index.ts src/lib/ai/__tests__/provider-registry.test.ts
716
- git commit -m "feat(ai): implement ProviderRegistry with Claude provider"
717
- ```
718
-
719
- ---
720
-
721
- ### Task 4: Add AiCapabilityConfig Prisma model
722
-
723
- **Files:**
724
- - Modify: `prisma/schema.prisma`
725
-
726
- - [ ] **Step 1: Add AiCapabilityConfig model to schema**
727
-
728
- Add after the `CliProfile` model block (after line 218 of `prisma/schema.prisma`):
729
-
730
- ```prisma
731
- model AiCapabilityConfig {
732
- id String @id @default(cuid())
733
- slot String @unique
734
- provider String @default("claude")
735
- mode String @default("cli")
736
- model String?
737
- createdAt DateTime @default(now())
738
- updatedAt DateTime @updatedAt
739
- }
740
- ```
741
-
742
- - [ ] **Step 2: Push schema to DB**
743
-
744
- Run: `cd /Users/liujunping/project/f/tower && pnpm db:push 2>&1 | tail -10`
745
- Expected: "Your database is now in sync with your Prisma schema"
746
-
747
- - [ ] **Step 3: Verify Prisma client generated**
748
-
749
- Run: `cd /Users/liujunping/project/f/tower && npx prisma generate 2>&1 | tail -5`
750
- Expected: "Generated Prisma Client"
751
-
752
- - [ ] **Step 4: Commit**
753
-
754
- ```bash
755
- git add prisma/schema.prisma
756
- git commit -m "feat(ai): add AiCapabilityConfig model for capability slot routing"
757
- ```
758
-
759
- ---
760
-
761
- ### Task 5: Implement capability-resolver
762
-
763
- **Files:**
764
- - Create: `src/lib/ai/capability-resolver.ts`
765
- - Create: `src/actions/ai-config-actions.ts`
766
-
767
- - [ ] **Step 1: Write the failing test**
768
-
769
- Create: `src/lib/ai/__tests__/capability-resolver.test.ts`
770
-
771
- ```typescript
772
- import { describe, it, expect, vi, beforeEach } from "vitest";
773
-
774
- // Mock DB before importing
775
- vi.mock("@/lib/db", () => ({
776
- db: {
777
- aiCapabilityConfig: {
778
- findUnique: vi.fn(),
779
- },
780
- },
781
- }));
782
-
783
- import { resolveCliAdapter, resolveQueryAdapter } from "../capability-resolver";
784
- import { providerRegistry } from "../providers";
785
- import { db } from "@/lib/db";
786
- import { AiProviderError } from "../types";
787
-
788
- describe("capability-resolver", () => {
789
- beforeEach(() => {
790
- vi.clearAllMocks();
791
- });
792
-
793
- describe("resolveCliAdapter", () => {
794
- it("returns Claude adapter when no config exists (default)", async () => {
795
- vi.mocked(db.aiCapabilityConfig.findUnique).mockResolvedValue(null);
796
- const result = await resolveCliAdapter("terminal");
797
- expect(result.provider.name).toBe("claude");
798
- expect(result.adapter).toBeDefined();
799
- });
800
-
801
- it("returns configured provider adapter", async () => {
802
- vi.mocked(db.aiCapabilityConfig.findUnique).mockResolvedValue({
803
- id: "1", slot: "terminal", provider: "claude", mode: "cli", model: "opus",
804
- createdAt: new Date(), updatedAt: new Date(),
805
- });
806
- const result = await resolveCliAdapter("terminal");
807
- expect(result.provider.name).toBe("claude");
808
- expect(result.model).toBe("opus");
809
- });
810
-
811
- it("throws AiProviderError for unknown provider", async () => {
812
- vi.mocked(db.aiCapabilityConfig.findUnique).mockResolvedValue({
813
- id: "1", slot: "terminal", provider: "nonexistent", mode: "cli", model: null,
814
- createdAt: new Date(), updatedAt: new Date(),
815
- });
816
- await expect(resolveCliAdapter("terminal")).rejects.toThrow(AiProviderError);
817
- });
818
- });
819
- });
820
- ```
821
-
822
- - [ ] **Step 2: Run test to verify it fails**
823
-
824
- Run: `cd /Users/liujunping/project/f/tower && pnpm vitest run src/lib/ai/__tests__/capability-resolver.test.ts 2>&1 | tail -20`
825
- Expected: FAIL
826
-
827
- - [ ] **Step 3: Implement capability-resolver**
828
-
829
- ```typescript
830
- // src/lib/ai/capability-resolver.ts
831
-
832
- import { db } from "@/lib/db";
833
- import { providerRegistry } from "./providers";
834
- import { AiProviderError } from "./types";
835
- import type { CliAdapter, AiQueryAdapter, ProviderDefinition, AiSlot } from "./types";
836
-
837
- const DEFAULT_PROVIDER = "claude";
838
- const DEFAULT_MODE = "cli";
839
-
840
- interface ResolvedCliAdapter {
841
- adapter: CliAdapter;
842
- provider: ProviderDefinition;
843
- model?: string;
844
- }
845
-
846
- interface ResolvedQueryAdapter {
847
- adapter: AiQueryAdapter;
848
- provider: ProviderDefinition;
849
- model?: string;
850
- }
851
-
852
- async function loadSlotConfig(slot: AiSlot) {
853
- return db.aiCapabilityConfig.findUnique({ where: { slot } });
854
- }
855
-
856
- export async function resolveCliAdapter(slot: "terminal"): Promise<ResolvedCliAdapter> {
857
- const config = await loadSlotConfig(slot);
858
- const providerName = config?.provider ?? DEFAULT_PROVIDER;
859
- const model = config?.model ?? undefined;
860
-
861
- const providerDef = providerRegistry.get(providerName);
862
- if (!providerDef) {
863
- throw new AiProviderError("CLI_NOT_FOUND", providerName, `Provider "${providerName}" 未注册`);
864
- }
865
-
866
- const adapter = providerDef.cli?.adapter;
867
- if (!adapter) {
868
- throw new AiProviderError("UNSUPPORTED_MODE", providerName, `Provider "${providerName}" 不支持 CLI 模式`);
869
- }
870
-
871
- return { adapter, provider: providerDef, model };
872
- }
873
-
874
- export async function resolveQueryAdapter(
875
- slot: "summary" | "dreaming" | "analysis" | "assistant"
876
- ): Promise<ResolvedQueryAdapter> {
877
- const config = await loadSlotConfig(slot);
878
- const providerName = config?.provider ?? DEFAULT_PROVIDER;
879
- const mode = config?.mode ?? DEFAULT_MODE;
880
- const model = config?.model ?? undefined;
881
-
882
- const providerDef = providerRegistry.get(providerName);
883
- if (!providerDef) {
884
- throw new AiProviderError("CLI_NOT_FOUND", providerName, `Provider "${providerName}" 未注册`);
885
- }
886
-
887
- const adapter = providerRegistry.getQueryAdapter(providerName, mode as "api" | "cli");
888
- if (!adapter) {
889
- throw new AiProviderError(
890
- "UNSUPPORTED_MODE",
891
- providerName,
892
- `Provider "${providerName}" 不支持 ${mode} 查询模式`
893
- );
894
- }
895
-
896
- // Validate model against provider's model list for this mode
897
- if (model) {
898
- const availableModels = providerDef.models[mode as "cli" | "api"] ?? [];
899
- if (availableModels.length > 0 && !availableModels.includes(model)) {
900
- throw new AiProviderError(
901
- "MODEL_NOT_AVAILABLE",
902
- providerName,
903
- `模型 "${model}" 在 ${providerName} ${mode} 模式下不可用`
904
- );
905
- }
906
- }
907
-
908
- return { adapter, provider: providerDef, model };
909
- }
910
- ```
911
-
912
- - [ ] **Step 4: Implement ai-config-actions (server actions for Settings UI)**
913
-
914
- ```typescript
915
- // src/actions/ai-config-actions.ts
916
- "use server";
917
-
918
- import { db } from "@/lib/db";
919
- import { revalidatePath } from "next/cache";
920
- import { providerRegistry } from "@/lib/ai/providers";
921
- import type { AiSlot } from "@/lib/ai/types";
922
-
923
- const VALID_SLOTS: AiSlot[] = ["terminal", "summary", "dreaming", "analysis", "assistant"];
924
-
925
- export async function getAiCapabilityConfigs() {
926
- return db.aiCapabilityConfig.findMany({ orderBy: { slot: "asc" } });
927
- }
928
-
929
- export async function updateAiCapabilityConfig(
930
- slot: string,
931
- data: { provider: string; mode: string; model?: string | null }
932
- ) {
933
- if (!VALID_SLOTS.includes(slot as AiSlot)) {
934
- throw new Error(`无效的插槽: ${slot}`);
935
- }
936
-
937
- // Validate provider exists
938
- const providerDef = providerRegistry.get(data.provider);
939
- if (!providerDef) {
940
- throw new Error(`未知的 Provider: ${data.provider}`);
941
- }
942
-
943
- // Terminal slot must be CLI mode
944
- if (slot === "terminal" && data.mode !== "cli") {
945
- throw new Error("终端执行只支持 CLI 模式");
946
- }
947
-
948
- await db.aiCapabilityConfig.upsert({
949
- where: { slot },
950
- create: {
951
- slot,
952
- provider: data.provider,
953
- mode: data.mode,
954
- model: data.model ?? null,
955
- },
956
- update: {
957
- provider: data.provider,
958
- mode: data.mode,
959
- model: data.model ?? null,
960
- },
961
- });
962
-
963
- revalidatePath("/settings");
964
- }
965
-
966
- export async function getAvailableProviders() {
967
- return providerRegistry.getAvailableProviders();
968
- }
969
- ```
970
-
971
- - [ ] **Step 5: Run test to verify it passes**
972
-
973
- Run: `cd /Users/liujunping/project/f/tower && pnpm vitest run src/lib/ai/__tests__/capability-resolver.test.ts 2>&1 | tail -20`
974
- Expected: All tests PASS
975
-
976
- - [ ] **Step 6: Commit**
977
-
978
- ```bash
979
- git add src/lib/ai/capability-resolver.ts src/actions/ai-config-actions.ts src/lib/ai/__tests__/capability-resolver.test.ts
980
- git commit -m "feat(ai): implement capability-resolver and ai-config actions"
981
- ```
982
-
983
- ---
984
-
985
- ### Task 6: Migrate agent-actions.ts to use CliAdapter
986
-
987
- This is the critical task — rewire the existing PTY execution functions to use the adapter layer while preserving exact behavior.
988
-
989
- **Files:**
990
- - Modify: `src/actions/agent-actions.ts`
991
- - Reference: `src/lib/ai/adapters/cli/claude-cli-adapter.ts`, `src/lib/ai/capability-resolver.ts`
992
-
993
- - [ ] **Step 1: Add imports at top of agent-actions.ts**
994
-
995
- Replace lines 6-8 of `src/actions/agent-actions.ts`:
996
-
997
- ```typescript
998
- // Before:
999
- import { createSession } from "@/lib/pty/session-store";
1000
- import { logger } from "@/lib/logger";
1001
- import { readConfigValue } from "@/lib/config-reader";
1002
-
1003
- // After:
1004
- import { createSession } from "@/lib/pty/session-store";
1005
- import { logger } from "@/lib/logger";
1006
- import { readConfigValue } from "@/lib/config-reader";
1007
- import { resolveCliAdapter } from "@/lib/ai/capability-resolver";
1008
- ```
1009
-
1010
- - [ ] **Step 2: Resolve adapter + build spawn result (replaces lines 597-620)**
1011
-
1012
- Replace the hardcoded env + args blocks with a single adapter flow. The adapter owns both args and env construction — the caller passes `spawnResult.env` directly to `createSession`.
1013
-
1014
- ```typescript
1015
- // ---- Replace lines 597-620 with: ----
1016
-
1017
- const { adapter: cliAdapter, provider: providerDef, model: configuredModel } = await resolveCliAdapter("terminal");
1018
-
1019
- // Build system prompt additions (instructions file + username)
1020
- let appendSystemPrompt = "";
1021
- if (instructionsFile) {
1022
- const { readFile } = await import("fs/promises");
1023
- appendSystemPrompt += await readFile(instructionsFile, "utf-8");
1024
- }
1025
- const usernameVal = await readConfigValue<string>("onboarding.username", "");
1026
- if (usernameVal) {
1027
- appendSystemPrompt += (appendSystemPrompt ? "\n" : "") + `The user's name is ${usernameVal}.`;
1028
- }
1029
-
1030
- // Adapter produces COMPLETE command + args + env
1031
- const spawnResult = cliAdapter.buildSpawnArgs({
1032
- taskId,
1033
- prompt: fullPrompt,
1034
- cwd,
1035
- profileArgs: [
1036
- ...profileBaseArgs,
1037
- ...(appendSystemPrompt ? ["--append-system-prompt", appendSystemPrompt] : []),
1038
- ...(configuredModel ? ["--model", configuredModel] : []),
1039
- ],
1040
- profileEnvVars,
1041
- envOverrides: cliAdapter.buildEnvOverrides({
1042
- taskId,
1043
- taskTitle: task.title,
1044
- apiUrl: `http://localhost:${process.env.PORT || "3000"}`,
1045
- callbackUrl: callbackUrl ?? undefined,
1046
- }),
1047
- });
1048
- ```
1049
-
1050
- - [ ] **Step 3: Update createSession call to use spawnResult (line 624)**
1051
-
1052
- ```typescript
1053
- // Before:
1054
- createSession(
1055
- taskId,
1056
- profileCommand,
1057
- claudeArgs,
1058
- cwd,
1059
- () => {},
1060
- async (exitCode) => { ... },
1061
- envOverrides,
1062
- ...
1063
-
1064
- // After — spawnResult.env replaces the separate envOverrides variable:
1065
- createSession(
1066
- taskId,
1067
- spawnResult.command,
1068
- spawnResult.args,
1069
- cwd,
1070
- () => {},
1071
- async (exitCode) => { ... },
1072
- spawnResult.env,
1073
- ...
1074
- ```
1075
-
1076
- - [ ] **Step 4: Update TaskExecution agent field (line 559)**
1077
-
1078
- ```typescript
1079
- // Before:
1080
- agent: "CLAUDE_CODE",
1081
-
1082
- // After:
1083
- agent: providerDef.agentFieldValue,
1084
- ```
1085
-
1086
- - [ ] **Step 6: Apply same pattern to resumePtyExecution**
1087
-
1088
- Apply the same adapter-based refactor to `resumePtyExecution` (lines 164-306):
1089
- - Add `resolveCliAdapter("terminal")` at the top
1090
- - Replace env overrides (lines 204-213) with `cliAdapter.buildEnvOverrides()`
1091
- - Replace Claude args (lines 229-236) with `cliAdapter.buildSpawnArgs({ resumeSessionId })`
1092
- - Replace `createSession(taskId, profileCommand, claudeArgs, ..., envOverrides, ...)` with `createSession(taskId, spawnResult.command, spawnResult.args, ..., spawnResult.env, ...)`
1093
- - `spawnResult.env` is the single source of truth — no separate `envOverrides`
1094
-
1095
- - [ ] **Step 7: Apply same pattern to continueLatestPtyExecution**
1096
-
1097
- Apply the same adapter-based refactor to `continueLatestPtyExecution` (lines 313-437):
1098
- - Same approach: `resolveCliAdapter` → `buildEnvOverrides` → `buildSpawnArgs({ continueLatest: true })` → pass `spawnResult.env` to `createSession`
1099
-
1100
- - [ ] **Step 8: Run existing tests**
1101
-
1102
- Run: `cd /Users/liujunping/project/f/tower && pnpm test:run 2>&1 | tail -30`
1103
- Expected: All existing tests PASS
1104
-
1105
- - [ ] **Step 9: Manual smoke test**
1106
-
1107
- Run: `cd /Users/liujunping/project/f/tower && pnpm dev`
1108
- Test: Create a task, start execution, verify Claude CLI spawns correctly, verify terminal works, verify stop/resume works.
1109
-
1110
- - [ ] **Step 10: Commit**
1111
-
1112
- ```bash
1113
- git add src/actions/agent-actions.ts
1114
- git commit -m "refactor(ai): migrate agent-actions to use CliAdapter"
1115
- ```
1116
-
1117
- ---
1118
-
1119
- ### Task 7: Update ALLOWED_COMMANDS to use registry
1120
-
1121
- **Files:**
1122
- - Modify: `src/actions/cli-profile-actions.ts`
1123
-
1124
- - [ ] **Step 1: Replace hardcoded ALLOWED_COMMANDS**
1125
-
1126
- ```typescript
1127
- // Before (line 7):
1128
- const ALLOWED_COMMANDS = ["claude", "claude-code"];
1129
-
1130
- // After:
1131
- import { providerRegistry } from "@/lib/ai/providers";
1132
-
1133
- function getAllowedCommands(): string[] {
1134
- const fromRegistry = providerRegistry.getAllowedCommands();
1135
- // Always include claude-code as alias
1136
- return [...new Set([...fromRegistry, "claude-code"])];
1137
- }
1138
- ```
1139
-
1140
- - [ ] **Step 2: Update validation to use function**
1141
-
1142
- ```typescript
1143
- // Before (line 34):
1144
- if (!ALLOWED_COMMANDS.includes(basename)) {
1145
- throw new Error(
1146
- `command must be one of: ${ALLOWED_COMMANDS.join(", ")} (got: ${basename})`
1147
- );
1148
- }
1149
-
1150
- // After:
1151
- const allowed = getAllowedCommands();
1152
- if (!allowed.includes(basename)) {
1153
- throw new Error(
1154
- `command must be one of: ${allowed.join(", ")} (got: ${basename})`
1155
- );
1156
- }
1157
- ```
1158
-
1159
- - [ ] **Step 3: Run existing cli-profile tests**
1160
-
1161
- Run: `cd /Users/liujunping/project/f/tower && pnpm vitest run src/actions/__tests__/cli-profile-actions.test.ts 2>&1 | tail -20`
1162
- Expected: PASS
1163
-
1164
- - [ ] **Step 4: Commit**
1165
-
1166
- ```bash
1167
- git add src/actions/cli-profile-actions.ts
1168
- git commit -m "refactor(ai): derive ALLOWED_COMMANDS from ProviderRegistry"
1169
- ```
1170
-
1171
- ---
1172
-
1173
- ### Task 8: Consolidate findClaudeBinary + migrate hooks route to adapter
1174
-
1175
- **Files:**
1176
- - Modify: `src/lib/claude-session.ts`
1177
- - Modify: `src/app/api/internal/assistant/chat/route.ts` (has duplicate findClaudeBinary)
1178
- - Modify: `src/app/api/internal/hooks/install/route.ts` (delegate to adapter)
1179
-
1180
- - [ ] **Step 1: Check for duplicate findClaudeBinary**
1181
-
1182
- Run: `cd /Users/liujunping/project/f/tower && grep -rn "findClaudeBinary\|resolveCommandPathSync.*claude" src/ --include="*.ts" | grep -v node_modules | grep -v __tests__`
1183
-
1184
- - [ ] **Step 2: Replace findClaudeBinary in claude-session.ts**
1185
-
1186
- ```typescript
1187
- // Before (lines 1-21):
1188
- import { resolveCommandPathSync } from "@/lib/platform";
1189
- // ...
1190
- function findClaudeBinary(): string { ... }
1191
-
1192
- // After:
1193
- import { ClaudeCliAdapter } from "@/lib/ai/adapters/cli/claude-cli-adapter";
1194
-
1195
- // Adapter resolves binary — single source of truth
1196
- const claudeAdapter = new ClaudeCliAdapter();
1197
- ```
1198
-
1199
- Then in `aiQuery()` (line 51), replace:
1200
- ```typescript
1201
- // Before:
1202
- pathToClaudeCodeExecutable: claudePath,
1203
-
1204
- // After (use adapter method — expose via public getter):
1205
- pathToClaudeCodeExecutable: claudeAdapter.resolveCommand(),
1206
- ```
1207
-
1208
- Note: `resolveCommand()` was already defined as public in Task 2.
1209
-
1210
- - [ ] **Step 3: Migrate assistant/chat/route.ts findClaudeBinary**
1211
-
1212
- Replace the duplicate `findClaudeBinary()` in `src/app/api/internal/assistant/chat/route.ts` with:
1213
- ```typescript
1214
- import { ClaudeCliAdapter } from "@/lib/ai/adapters/cli/claude-cli-adapter";
1215
- const claudeAdapter = new ClaudeCliAdapter();
1216
- // Replace findClaudeBinary() calls with claudeAdapter.resolveCommand()
1217
- ```
1218
-
1219
- - [ ] **Step 4: Migrate hooks/install/route.ts to delegate to adapter**
1220
-
1221
- Refactor `src/app/api/internal/hooks/install/route.ts` to use `ClaudeCliAdapter` for hook management:
1222
- ```typescript
1223
- import { ClaudeCliAdapter } from "@/lib/ai/adapters/cli/claude-cli-adapter";
1224
- const claudeAdapter = new ClaudeCliAdapter();
1225
-
1226
- // GET: return claudeAdapter.isHooksInstalled()
1227
- // POST: call claudeAdapter.installHooks(apiUrl)
1228
- // DELETE: call claudeAdapter.uninstallHooks()
1229
- ```
1230
-
1231
- This eliminates duplicate readSettings/writeSettings/getPostToolUseArray logic — adapter is single source of truth.
1232
-
1233
- - [ ] **Step 5: Run tests**
1234
-
1235
- Run: `cd /Users/liujunping/project/f/tower && pnpm test:run 2>&1 | tail -20`
1236
- Expected: PASS
1237
-
1238
- - [ ] **Step 6: Commit**
1239
-
1240
- ```bash
1241
- git add src/lib/claude-session.ts src/lib/ai/adapters/cli/claude-cli-adapter.ts src/app/api/internal/assistant/chat/route.ts src/app/api/internal/hooks/install/route.ts
1242
- git commit -m "refactor(ai): consolidate findClaudeBinary and hooks into ClaudeCliAdapter"
1243
- ```
1244
-
1245
- ---
1246
-
1247
- ### Task 9: Add i18n keys for AI capability config
1248
-
1249
- **Files:**
1250
- - Modify: `src/lib/i18n/zh.ts`
1251
- - Modify: `src/lib/i18n/en.ts`
1252
-
1253
- Note: i18n uses flat key format (`"section.key": "value"`), not nested JSON.
1254
-
1255
- - [ ] **Step 1: Check existing i18n structure for settings**
1256
-
1257
- Run: `cd /Users/liujunping/project/f/tower && grep "settings\." src/lib/i18n/zh.ts | head -10`
1258
-
1259
- - [ ] **Step 2: Add zh keys**
1260
-
1261
- Add to the flat export object in `src/lib/i18n/zh.ts`:
1262
-
1263
- ```typescript
1264
- "aiConfig.title": "AI 能力配置",
1265
- "aiConfig.description": "为每个 AI 功能场景独立配置 Provider 和模型",
1266
- "aiConfig.terminal": "终端执行",
1267
- "aiConfig.summary": "会话总结",
1268
- "aiConfig.dreaming": "知识沉淀",
1269
- "aiConfig.analysis": "项目分析",
1270
- "aiConfig.assistant": "助手聊天",
1271
- "aiConfig.provider": "Provider",
1272
- "aiConfig.mode": "模式",
1273
- "aiConfig.model": "模型",
1274
- "aiConfig.cli": "CLI(订阅)",
1275
- "aiConfig.api": "API(按量)",
1276
- "aiConfig.default": "默认",
1277
- "aiConfig.notConfigured": "未配置",
1278
- "aiConfig.cliNotFound": "CLI 未安装",
1279
- "aiConfig.apiKeyMissing": "API Key 未配置",
1280
- "aiConfig.saved": "已保存",
1281
- ```
1282
-
1283
- - [ ] **Step 3: Add en keys**
1284
-
1285
- Add to the flat export object in `src/lib/i18n/en.ts`:
1286
-
1287
- ```typescript
1288
- "aiConfig.title": "AI Capability Config",
1289
- "aiConfig.description": "Configure provider and model for each AI feature independently",
1290
- "aiConfig.terminal": "Terminal Execution",
1291
- "aiConfig.summary": "Session Summary",
1292
- "aiConfig.dreaming": "Knowledge Distillation",
1293
- "aiConfig.analysis": "Project Analysis",
1294
- "aiConfig.assistant": "Assistant Chat",
1295
- "aiConfig.provider": "Provider",
1296
- "aiConfig.mode": "Mode",
1297
- "aiConfig.model": "Model",
1298
- "aiConfig.cli": "CLI (Subscription)",
1299
- "aiConfig.api": "API (Pay-per-use)",
1300
- "aiConfig.default": "Default",
1301
- "aiConfig.notConfigured": "Not configured",
1302
- "aiConfig.cliNotFound": "CLI not installed",
1303
- "aiConfig.apiKeyMissing": "API key not configured",
1304
- "aiConfig.saved": "Saved",
1305
- ```
1306
-
1307
- - [ ] **Step 4: Commit**
1308
-
1309
- ```bash
1310
- git add src/i18n/locales/zh.json src/i18n/locales/en.json
1311
- git commit -m "feat(i18n): add AI capability config translation keys"
1312
- ```
1313
-
1314
- ---
1315
-
1316
- ### Task 10: Full regression test
1317
-
1318
- - [ ] **Step 1: Run all unit tests**
1319
-
1320
- Run: `cd /Users/liujunping/project/f/tower && pnpm test:run 2>&1 | tail -30`
1321
- Expected: All PASS
1322
-
1323
- - [ ] **Step 2: TypeScript type check**
1324
-
1325
- Run: `cd /Users/liujunping/project/f/tower && npx tsc --noEmit 2>&1 | tail -20`
1326
- Expected: No errors
1327
-
1328
- - [ ] **Step 3: Start dev server and smoke test**
1329
-
1330
- Run: `cd /Users/liujunping/project/f/tower && pnpm dev`
1331
-
1332
- Manual checks:
1333
- 1. Create task → Start execution → Claude CLI spawns in terminal ✓
1334
- 2. Stop execution → Summary generates ✓
1335
- 3. Resume execution → `--resume` works ✓
1336
- 4. Continue execution → `--continue` works ✓
1337
- 5. Settings page → CLI Profile still editable ✓
1338
- 6. Hook install/uninstall still works ✓
1339
-
1340
- - [ ] **Step 4: Final commit with all clean-up**
1341
-
1342
- ```bash
1343
- git add -A
1344
- git commit -m "chore(ai): Phase 1 complete — CLI adapter layer with Claude migration"
1345
- ```