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.
- package/bin/tower.mjs +5 -15
- package/next.config.ts +1 -8
- package/package.json +10 -11
- package/prisma/dev.db +0 -0
- package/public/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
- package/public/vs/abap-D-t0cyap.js +1 -0
- package/public/vs/apex-CcIm7xu6.js +1 -0
- package/public/vs/assets/css.worker-HnVq6Ewq.js +93 -0
- package/public/vs/assets/editor.worker-Be8ye1pW.js +26 -0
- package/public/vs/assets/html.worker-B51mlPHg.js +470 -0
- package/public/vs/assets/json.worker-DKiEKt88.js +58 -0
- package/public/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
- package/public/vs/azcli-BA0tQDCg.js +1 -0
- package/public/vs/basic-languages/monaco.contribution.js +1 -0
- package/public/vs/bat-C397hTD6.js +1 -0
- package/public/vs/bicep-DF5aW17k.js +2 -0
- package/public/vs/cameligo-plsz8qhj.js +1 -0
- package/public/vs/clojure-Y2auQMzK.js +1 -0
- package/public/vs/coffee-Bu45yuWE.js +1 -0
- package/public/vs/cpp-CkKPQIni.js +1 -0
- package/public/vs/csharp-CX28MZyh.js +1 -0
- package/public/vs/csp-D8uWnyxW.js +1 -0
- package/public/vs/css-CaeNmE3S.js +3 -0
- package/public/vs/cssMode-CjiAH6dQ.js +1 -0
- package/public/vs/cypher-DVThT8BS.js +1 -0
- package/public/vs/dart-CmGfCvrO.js +1 -0
- package/public/vs/dockerfile-CZqqYdch.js +1 -0
- package/public/vs/ecl-30fUercY.js +1 -0
- package/public/vs/editor/editor.main.css +1 -0
- package/public/vs/editor/editor.main.js +5 -0
- package/public/vs/editor.api-CalNCsUg.js +903 -0
- package/public/vs/elixir-xjPaIfzF.js +1 -0
- package/public/vs/flow9-DqtmStfK.js +1 -0
- package/public/vs/freemarker2-Cz_sV6Md.js +3 -0
- package/public/vs/fsharp-BOMdg4U1.js +1 -0
- package/public/vs/go-D_hbi-Jt.js +1 -0
- package/public/vs/graphql-CKUU4kLG.js +1 -0
- package/public/vs/handlebars-OwglfO-1.js +1 -0
- package/public/vs/hcl-DTaboeZW.js +1 -0
- package/public/vs/html-Pa1xEWsY.js +1 -0
- package/public/vs/htmlMode-Bz67EXwp.js +1 -0
- package/public/vs/ini-CsNwO04R.js +1 -0
- package/public/vs/java-CI4ZMsH9.js +1 -0
- package/public/vs/javascript-PczUCGdz.js +1 -0
- package/public/vs/jsonMode-DULH5oaX.js +7 -0
- package/public/vs/julia-BwzEvaQw.js +1 -0
- package/public/vs/kotlin-IUYPiTV8.js +1 -0
- package/public/vs/language/css/monaco.contribution.js +1 -0
- package/public/vs/language/html/monaco.contribution.js +1 -0
- package/public/vs/language/json/monaco.contribution.js +1 -0
- package/public/vs/language/typescript/monaco.contribution.js +1 -0
- package/public/vs/less-C0eDYdqa.js +2 -0
- package/public/vs/lexon-iON-Kj97.js +1 -0
- package/public/vs/liquid-DqKjdPGy.js +1 -0
- package/public/vs/loader.js +1368 -0
- package/public/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
- package/public/vs/lua-DtygF91M.js +1 -0
- package/public/vs/m3-CsR4AuFi.js +1 -0
- package/public/vs/markdown-C_rD0bIw.js +1 -0
- package/public/vs/mdx-DEWtB1K5.js +1 -0
- package/public/vs/mips-CiYP61RB.js +1 -0
- package/public/vs/monaco.contribution-D2OdxNBt.js +1 -0
- package/public/vs/monaco.contribution-DO3azKX8.js +1 -0
- package/public/vs/monaco.contribution-EcChJV6a.js +1 -0
- package/public/vs/monaco.contribution-qLAYrEOP.js +1 -0
- package/public/vs/msdax-C38-sJlp.js +1 -0
- package/public/vs/mysql-CdtbpvbG.js +1 -0
- package/public/vs/nls.messages-loader.js +1 -0
- package/public/vs/nls.messages.cs.js.js +17 -0
- package/public/vs/nls.messages.de.js.js +17 -0
- package/public/vs/nls.messages.es.js.js +17 -0
- package/public/vs/nls.messages.fr.js.js +15 -0
- package/public/vs/nls.messages.it.js.js +15 -0
- package/public/vs/nls.messages.ja.js.js +17 -0
- package/public/vs/nls.messages.js.js +10 -0
- package/public/vs/nls.messages.ko.js.js +25 -0
- package/public/vs/nls.messages.pl.js.js +17 -0
- package/public/vs/nls.messages.pt-br.js.js +6 -0
- package/public/vs/nls.messages.ru.js.js +17 -0
- package/public/vs/nls.messages.tr.js.js +15 -0
- package/public/vs/nls.messages.zh-cn.js.js +17 -0
- package/public/vs/nls.messages.zh-tw.js.js +15 -0
- package/public/vs/objective-c-CntZFaHX.js +1 -0
- package/public/vs/pascal-r6kuqfl_.js +1 -0
- package/public/vs/pascaligo-BiXoTmXh.js +1 -0
- package/public/vs/perl-DABw_TcH.js +1 -0
- package/public/vs/pgsql-me_jFXeX.js +1 -0
- package/public/vs/php-D_kh-9LK.js +1 -0
- package/public/vs/pla-VfZjczW0.js +1 -0
- package/public/vs/postiats-BBSzz8Pk.js +1 -0
- package/public/vs/powerquery-Dt-g_2cc.js +1 -0
- package/public/vs/powershell-B-7ap1zc.js +1 -0
- package/public/vs/protobuf-BmtuEB1A.js +2 -0
- package/public/vs/pug-BRpRNeEb.js +1 -0
- package/public/vs/python-Cr0UkIbn.js +1 -0
- package/public/vs/qsharp-BzsFaUU9.js +1 -0
- package/public/vs/r-f8dDdrp4.js +1 -0
- package/public/vs/razor-BYAHOTkz.js +1 -0
- package/public/vs/redis-fvZQY4PI.js +1 -0
- package/public/vs/redshift-45Et0LQi.js +1 -0
- package/public/vs/restructuredtext-C7UUFKFD.js +1 -0
- package/public/vs/ruby-CZO8zYTz.js +1 -0
- package/public/vs/rust-Bfetafyc.js +1 -0
- package/public/vs/sb-3GYllVck.js +1 -0
- package/public/vs/scala-foMgrKo1.js +1 -0
- package/public/vs/scheme-CHdMtr7p.js +1 -0
- package/public/vs/scss-C1cmLt9V.js +3 -0
- package/public/vs/shell-ClXCKCEW.js +1 -0
- package/public/vs/solidity-MZ6ExpPy.js +1 -0
- package/public/vs/sophia-DWkuSsPQ.js +1 -0
- package/public/vs/sparql-AUGFYSyk.js +1 -0
- package/public/vs/sql-32GpJSV2.js +1 -0
- package/public/vs/st-CuDFIVZ_.js +1 -0
- package/public/vs/swift-n-2HociN.js +3 -0
- package/public/vs/systemverilog-Ch4vA8Yt.js +1 -0
- package/public/vs/tcl-D74tq1nH.js +1 -0
- package/public/vs/tsMode-CZz1Umrk.js +11 -0
- package/public/vs/twig-C6taOxMV.js +1 -0
- package/public/vs/typescript-DfOrAzoV.js +1 -0
- package/public/vs/typespec-D-PIh9Xw.js +1 -0
- package/public/vs/vb-Dyb2648j.js +1 -0
- package/public/vs/wgsl-BhLXMOR0.js +298 -0
- package/public/vs/workers-DcJshg-q.js +1 -0
- package/public/vs/xml-CdsdnY8S.js +1 -0
- package/public/vs/yaml-DYGvmE88.js +1 -0
- package/src/actions/__tests__/agent-actions-username.test.ts +30 -0
- package/src/actions/__tests__/asset-actions.test.ts +251 -0
- package/src/actions/__tests__/assistant-actions.test.ts +20 -0
- package/src/actions/__tests__/cli-profile-actions.test.ts +243 -0
- package/src/actions/__tests__/label-actions.test.ts +187 -0
- package/src/actions/__tests__/note-actions.test.ts +237 -0
- package/src/actions/__tests__/onboarding-actions.test.ts +265 -0
- package/src/actions/__tests__/project-actions.test.ts +179 -0
- package/src/actions/__tests__/prompt-actions.test.ts +213 -0
- package/src/actions/__tests__/report-actions.test.ts +246 -0
- package/src/actions/__tests__/search-code-actions.test.ts +308 -0
- package/src/actions/__tests__/task-actions-overview.test.ts +58 -0
- package/src/actions/__tests__/task-actions-pin.test.ts +79 -0
- package/src/actions/__tests__/workspace-actions.test.ts +256 -0
- package/src/components/layout/__tests__/top-bar-username.test.tsx +24 -0
- package/src/components/onboarding/__tests__/onboarding-wizard.test.tsx +185 -0
- package/src/hooks/__tests__/sse-event-reducer.test.ts +263 -0
- package/src/hooks/__tests__/use-assistant-chat.test.ts +34 -0
- package/src/hooks/__tests__/use-image-upload.test.ts +443 -0
- package/src/lib/__tests__/assistant-message-converter.test.ts +162 -0
- package/src/lib/__tests__/assistant-sessions.test.ts +253 -0
- package/src/lib/__tests__/build-multimodal-prompt.test.ts +173 -0
- package/src/lib/__tests__/config-reader.test.ts +75 -0
- package/src/lib/__tests__/diff-parser.test.ts +212 -0
- package/src/lib/__tests__/execution-summary.test.ts +237 -0
- package/src/lib/__tests__/file-serve.test.ts +178 -0
- package/src/lib/__tests__/file-utils.test.ts +177 -0
- package/src/lib/__tests__/internal-api-guard.test.ts +151 -0
- package/src/lib/__tests__/logger.test.ts +181 -0
- package/src/lib/__tests__/platform.test.ts +566 -0
- package/src/lib/__tests__/reveal-route-security.test.ts +65 -0
- package/src/lib/__tests__/schemas.test.ts +377 -0
- package/src/lib/__tests__/terminal-link-provider.test.ts +160 -0
- package/src/lib/__tests__/upload-route-security.test.ts +120 -0
- package/src/lib/ai/__tests__/capability-resolver.test.ts +71 -0
- package/src/lib/ai/__tests__/claude-cli-adapter.test.ts +103 -0
- package/src/lib/ai/__tests__/provider-registry.test.ts +74 -0
- package/src/lib/pty/__tests__/ws-server-assistant.test.ts +7 -0
- package/.next/standalone/tower/.claude/rules/process-lifecycle.md +0 -31
- package/.next/standalone/tower/.claude/rules/security.md +0 -29
- package/.next/standalone/tower/.claude/rules/ui.md +0 -105
- package/.next/standalone/tower/AGENTS.md +0 -325
- package/.next/standalone/tower/CHANGELOG.md +0 -63
- package/.next/standalone/tower/CLAUDE.md +0 -33
- package/.next/standalone/tower/LICENSE +0 -21
- package/.next/standalone/tower/README.md +0 -214
- package/.next/standalone/tower/README.zh.md +0 -212
- package/.next/standalone/tower/bin/tower.mjs +0 -180
- package/.next/standalone/tower/docs/README.md +0 -51
- package/.next/standalone/tower/docs/ai/README.md +0 -91
- package/.next/standalone/tower/docs/ai/cli-abstraction-design.md +0 -398
- package/.next/standalone/tower/docs/ai/cli-abstraction-plan.md +0 -1345
- package/.next/standalone/tower/docs/assets-notes/README.md +0 -41
- package/.next/standalone/tower/docs/assistant/README.md +0 -48
- package/.next/standalone/tower/docs/board/README.md +0 -36
- package/.next/standalone/tower/docs/diagrams/tower-ai-architecture-en.html +0 -259
- package/.next/standalone/tower/docs/diagrams/tower-ai-architecture.html +0 -259
- package/.next/standalone/tower/docs/diagrams/tower-data-model-en.html +0 -344
- package/.next/standalone/tower/docs/diagrams/tower-data-model.html +0 -344
- package/.next/standalone/tower/docs/diagrams/tower-module-map-en.html +0 -176
- package/.next/standalone/tower/docs/diagrams/tower-module-map.html +0 -176
- package/.next/standalone/tower/docs/diagrams/tower-system-architecture-en.html +0 -336
- package/.next/standalone/tower/docs/diagrams/tower-system-architecture.html +0 -336
- package/.next/standalone/tower/docs/diagrams/tower-task-lifecycle-en.html +0 -251
- package/.next/standalone/tower/docs/diagrams/tower-task-lifecycle.html +0 -251
- package/.next/standalone/tower/docs/en/guide/architecture.md +0 -79
- package/.next/standalone/tower/docs/en/guide/diagrams.md +0 -24
- package/.next/standalone/tower/docs/en/guide/getting-started.md +0 -80
- package/.next/standalone/tower/docs/en/guide/introduction.md +0 -84
- package/.next/standalone/tower/docs/en/index.md +0 -36
- package/.next/standalone/tower/docs/en/modules/ai.md +0 -110
- package/.next/standalone/tower/docs/en/modules/assets-notes.md +0 -56
- package/.next/standalone/tower/docs/en/modules/assistant.md +0 -63
- package/.next/standalone/tower/docs/en/modules/board.md +0 -44
- package/.next/standalone/tower/docs/en/modules/git.md +0 -55
- package/.next/standalone/tower/docs/en/modules/i18n.md +0 -35
- package/.next/standalone/tower/docs/en/modules/mcp.md +0 -62
- package/.next/standalone/tower/docs/en/modules/missions.md +0 -47
- package/.next/standalone/tower/docs/en/modules/project.md +0 -57
- package/.next/standalone/tower/docs/en/modules/search.md +0 -49
- package/.next/standalone/tower/docs/en/modules/settings.md +0 -63
- package/.next/standalone/tower/docs/en/modules/task.md +0 -82
- package/.next/standalone/tower/docs/en/modules/terminal.md +0 -73
- package/.next/standalone/tower/docs/en/modules/workspace.md +0 -61
- package/.next/standalone/tower/docs/git/README.md +0 -42
- package/.next/standalone/tower/docs/guide/architecture.md +0 -29
- package/.next/standalone/tower/docs/guide/diagrams.md +0 -24
- package/.next/standalone/tower/docs/guide/getting-started.md +0 -80
- package/.next/standalone/tower/docs/guide/introduction.md +0 -84
- package/.next/standalone/tower/docs/i18n/README.md +0 -23
- package/.next/standalone/tower/docs/index.md +0 -36
- package/.next/standalone/tower/docs/mcp/README.md +0 -50
- package/.next/standalone/tower/docs/missions/README.md +0 -40
- package/.next/standalone/tower/docs/modules/ai.md +0 -104
- package/.next/standalone/tower/docs/modules/assets-notes.md +0 -68
- package/.next/standalone/tower/docs/modules/assistant.md +0 -74
- package/.next/standalone/tower/docs/modules/board.md +0 -56
- package/.next/standalone/tower/docs/modules/git.md +0 -61
- package/.next/standalone/tower/docs/modules/i18n.md +0 -38
- package/.next/standalone/tower/docs/modules/mcp.md +0 -68
- package/.next/standalone/tower/docs/modules/missions.md +0 -54
- package/.next/standalone/tower/docs/modules/project.md +0 -65
- package/.next/standalone/tower/docs/modules/search.md +0 -57
- package/.next/standalone/tower/docs/modules/settings.md +0 -72
- package/.next/standalone/tower/docs/modules/task.md +0 -82
- package/.next/standalone/tower/docs/modules/terminal.md +0 -70
- package/.next/standalone/tower/docs/modules/workspace.md +0 -68
- package/.next/standalone/tower/docs/package.json +0 -11
- package/.next/standalone/tower/docs/pnpm-lock.yaml +0 -1620
- package/.next/standalone/tower/docs/postcss.config.mjs +0 -2
- package/.next/standalone/tower/docs/project/README.md +0 -45
- package/.next/standalone/tower/docs/public/banner.png +0 -0
- package/.next/standalone/tower/docs/public/diagrams/tower-ai-architecture-en.html +0 -259
- package/.next/standalone/tower/docs/public/diagrams/tower-ai-architecture.html +0 -259
- package/.next/standalone/tower/docs/public/diagrams/tower-data-model-en.html +0 -344
- package/.next/standalone/tower/docs/public/diagrams/tower-data-model.html +0 -344
- package/.next/standalone/tower/docs/public/diagrams/tower-module-map-en.html +0 -176
- package/.next/standalone/tower/docs/public/diagrams/tower-module-map.html +0 -176
- package/.next/standalone/tower/docs/public/diagrams/tower-system-architecture-en.html +0 -336
- package/.next/standalone/tower/docs/public/diagrams/tower-system-architecture.html +0 -336
- package/.next/standalone/tower/docs/public/diagrams/tower-task-lifecycle-en.html +0 -251
- package/.next/standalone/tower/docs/public/diagrams/tower-task-lifecycle.html +0 -251
- package/.next/standalone/tower/docs/search/README.md +0 -37
- package/.next/standalone/tower/docs/settings/README.md +0 -43
- package/.next/standalone/tower/docs/task/README.md +0 -67
- package/.next/standalone/tower/docs/terminal/README.md +0 -58
- package/.next/standalone/tower/docs/workspace/README.md +0 -50
- package/.next/standalone/tower/docs/zh/guide/architecture.md +0 -29
- package/.next/standalone/tower/docs/zh/guide/diagrams.md +0 -24
- package/.next/standalone/tower/docs/zh/guide/getting-started.md +0 -80
- package/.next/standalone/tower/docs/zh/guide/introduction.md +0 -66
- package/.next/standalone/tower/docs/zh/index.md +0 -23
- package/.next/standalone/tower/docs/zh/modules/ai.md +0 -104
- package/.next/standalone/tower/docs/zh/modules/assets-notes.md +0 -68
- package/.next/standalone/tower/docs/zh/modules/assistant.md +0 -74
- package/.next/standalone/tower/docs/zh/modules/board.md +0 -56
- package/.next/standalone/tower/docs/zh/modules/git.md +0 -61
- package/.next/standalone/tower/docs/zh/modules/i18n.md +0 -38
- package/.next/standalone/tower/docs/zh/modules/mcp.md +0 -68
- package/.next/standalone/tower/docs/zh/modules/missions.md +0 -54
- package/.next/standalone/tower/docs/zh/modules/project.md +0 -65
- package/.next/standalone/tower/docs/zh/modules/search.md +0 -57
- package/.next/standalone/tower/docs/zh/modules/settings.md +0 -72
- package/.next/standalone/tower/docs/zh/modules/task.md +0 -82
- package/.next/standalone/tower/docs/zh/modules/terminal.md +0 -70
- package/.next/standalone/tower/docs/zh/modules/workspace.md +0 -68
- package/.next/standalone/tower/eslint.config.mjs +0 -18
- package/.next/standalone/tower/next.config.ts +0 -15
- package/.next/standalone/tower/package.json +0 -117
- package/.next/standalone/tower/playwright.config.ts +0 -28
- package/.next/standalone/tower/pnpm-lock.yaml +0 -10524
- package/.next/standalone/tower/pnpm-workspace.yaml +0 -6
- package/.next/standalone/tower/prisma/init-fts.ts +0 -23
- package/.next/standalone/tower/prisma/schema.prisma +0 -290
- package/.next/standalone/tower/prompts/backend-developer.md +0 -20
- package/.next/standalone/tower/prompts/code-reviewer.md +0 -19
- package/.next/standalone/tower/prompts/frontend-developer.md +0 -20
- package/.next/standalone/tower/prompts/product-manager.md +0 -16
- package/.next/standalone/tower/public/banner.jpg +0 -0
- package/.next/standalone/tower/public/logo.png +0 -0
- package/.next/standalone/tower/scripts/copy-monaco.js +0 -27
- package/.next/standalone/tower/scripts/init-db.ts +0 -74
- package/.next/standalone/tower/scripts/migrate-data.ts +0 -102
- package/.next/standalone/tower/server.js +0 -38
- package/.next/standalone/tower/src/app/favicon.ico +0 -0
- package/.next/standalone/tower/src/mcp/db.ts +0 -11
- package/.next/standalone/tower/src/mcp/index.ts +0 -15
- package/.next/standalone/tower/src/mcp/server.ts +0 -52
- package/.next/standalone/tower/src/mcp/tools/knowledge-tools.ts +0 -100
- package/.next/standalone/tower/src/mcp/tools/label-tools.ts +0 -70
- package/.next/standalone/tower/src/mcp/tools/note-asset-tools.ts +0 -271
- package/.next/standalone/tower/src/mcp/tools/project-tools.ts +0 -79
- package/.next/standalone/tower/src/mcp/tools/report-tools.ts +0 -214
- package/.next/standalone/tower/src/mcp/tools/search-tools.ts +0 -32
- package/.next/standalone/tower/src/mcp/tools/task-tools.ts +0 -250
- package/.next/standalone/tower/src/mcp/tools/terminal-tools.ts +0 -154
- package/.next/standalone/tower/src/mcp/tools/workspace-tools.ts +0 -73
- package/.next/standalone/tower/tests/e2e/chat-flow.spec.ts +0 -249
- package/.next/standalone/tower/tests/e2e/notes-assets.spec.ts +0 -287
- package/.next/standalone/tower/tests/e2e/search.spec.ts +0 -186
- package/.next/standalone/tower/tests/e2e/settings-flow.spec.ts +0 -250
- package/.next/standalone/tower/tests/e2e/settings.spec.ts +0 -261
- package/.next/standalone/tower/tests/e2e/smoke.spec.ts +0 -317
- package/.next/standalone/tower/tests/e2e/task-flow.spec.ts +0 -287
- package/.next/standalone/tower/tests/e2e/workbench.spec.ts +0 -307
- package/.next/standalone/tower/tests/setup.ts +0 -15
- package/.next/standalone/tower/tests/unit/actions/agent-actions.test.ts +0 -157
- package/.next/standalone/tower/tests/unit/actions/config-actions.test.ts +0 -170
- package/.next/standalone/tower/tests/unit/actions/file-actions.test.ts +0 -283
- package/.next/standalone/tower/tests/unit/actions/git-actions.test.ts +0 -34
- package/.next/standalone/tower/tests/unit/actions/preview-actions.test.ts +0 -177
- package/.next/standalone/tower/tests/unit/actions/search-actions.test.ts +0 -358
- package/.next/standalone/tower/tests/unit/actions/task-actions.test.ts +0 -173
- package/.next/standalone/tower/tests/unit/api/diff-route.test.ts +0 -9
- package/.next/standalone/tower/tests/unit/api/file-serving.test.ts +0 -88
- package/.next/standalone/tower/tests/unit/api/merge-route.test.ts +0 -9
- package/.next/standalone/tower/tests/unit/api/stream-persist-result.test.ts +0 -7
- package/.next/standalone/tower/tests/unit/api/stream-send-back.test.ts +0 -7
- package/.next/standalone/tower/tests/unit/components/asset-item.test.tsx +0 -105
- package/.next/standalone/tower/tests/unit/components/asset-list.test.tsx +0 -51
- package/.next/standalone/tower/tests/unit/components/assets/asset-upload.test.tsx +0 -70
- package/.next/standalone/tower/tests/unit/components/board-stats.test.tsx +0 -32
- package/.next/standalone/tower/tests/unit/components/category-filter.test.tsx +0 -58
- package/.next/standalone/tower/tests/unit/components/cli-adapter-tester.test.tsx +0 -160
- package/.next/standalone/tower/tests/unit/components/create-task-dialog.test.tsx +0 -162
- package/.next/standalone/tower/tests/unit/components/file-tree.test.tsx +0 -172
- package/.next/standalone/tower/tests/unit/components/note-card.test.tsx +0 -71
- package/.next/standalone/tower/tests/unit/components/note-editor.test.tsx +0 -39
- package/.next/standalone/tower/tests/unit/components/prompts-config.test.tsx +0 -200
- package/.next/standalone/tower/tests/unit/components/search-dialog.test.tsx +0 -223
- package/.next/standalone/tower/tests/unit/components/system-config.test.tsx +0 -32
- package/.next/standalone/tower/tests/unit/hooks/hook-registration.test.ts +0 -89
- package/.next/standalone/tower/tests/unit/hooks/stop-hook.test.ts +0 -108
- package/.next/standalone/tower/tests/unit/lib/asset-actions.test.ts +0 -257
- package/.next/standalone/tower/tests/unit/lib/file-utils.test.ts +0 -102
- package/.next/standalone/tower/tests/unit/lib/fs-security.test.ts +0 -34
- package/.next/standalone/tower/tests/unit/lib/fts.test.ts +0 -214
- package/.next/standalone/tower/tests/unit/lib/git-url.test.ts +0 -258
- package/.next/standalone/tower/tests/unit/lib/instrumentation.test.ts +0 -86
- package/.next/standalone/tower/tests/unit/lib/local-path-to-api-url.test.ts +0 -40
- package/.next/standalone/tower/tests/unit/lib/mime-magic.test.ts +0 -95
- package/.next/standalone/tower/tests/unit/lib/note-actions.test.ts +0 -296
- package/.next/standalone/tower/tests/unit/lib/preview-process-manager.test.ts +0 -107
- package/.next/standalone/tower/tests/unit/lib/process-manager.test.ts +0 -10
- package/.next/standalone/tower/tests/unit/lib/pty-session.test.ts +0 -171
- package/.next/standalone/tower/tests/unit/lib/search.test.ts +0 -289
- package/.next/standalone/tower/tests/unit/lib/session-store.test.ts +0 -111
- package/.next/standalone/tower/tests/unit/lib/utils.test.ts +0 -33
- package/.next/standalone/tower/tests/unit/lib/worktree.test.ts +0 -190
- package/.next/standalone/tower/tests/unit/mcp/identify-project.test.ts +0 -217
- package/.next/standalone/tower/tests/unit/mcp/manage-assets.test.ts +0 -199
- package/.next/standalone/tower/tests/unit/mcp/manage-notes.test.ts +0 -304
- package/.next/standalone/tower/tests/unit/mcp/search-tools.test.ts +0 -186
- package/.next/standalone/tower/tests/unit/missions-merge.test.ts +0 -43
- package/.next/standalone/tower/tower-studio-0.1.1.tgz +0 -0
- package/.next/standalone/tower/tsconfig.json +0 -34
- package/.next/standalone/tower/vitest.config.ts +0 -17
- package/.next/static/a6HR9cTK7s4aKfE5dWzjz/_buildManifest.js +0 -11
- package/.next/static/a6HR9cTK7s4aKfE5dWzjz/_clientMiddlewareManifest.js +0 -1
- package/.next/static/a6HR9cTK7s4aKfE5dWzjz/_ssgManifest.js +0 -1
- package/.next/static/chunks/00z-g3x93ngvn.js +0 -1
- package/.next/static/chunks/024pdsc27tjdd.js +0 -5
- package/.next/static/chunks/02dd83gbiv778.js +0 -1
- package/.next/static/chunks/03e.4ymu.j5wl.js +0 -1
- package/.next/static/chunks/03~yq9q893hmn.js +0 -1
- package/.next/static/chunks/05-b9qqm3av9~.js +0 -1
- package/.next/static/chunks/05~v02mkan5z..js +0 -1
- package/.next/static/chunks/0abtpeymj-58i.js +0 -1
- package/.next/static/chunks/0b6us7uq72u-d.js +0 -1
- package/.next/static/chunks/0drgc-oztq6o-.css +0 -1
- package/.next/static/chunks/0eaa2lmymh2fx.js +0 -1
- package/.next/static/chunks/0gb82g.6g90mn.js +0 -1
- package/.next/static/chunks/0gnm22yv~f54b.js +0 -1
- package/.next/static/chunks/0j9qriqni_r1..js +0 -2
- package/.next/static/chunks/0k.u8sxy~e469.js +0 -4
- package/.next/static/chunks/0lnhjf2a~jaco.js +0 -83
- package/.next/static/chunks/0lvd52mjiit6s.js +0 -1
- package/.next/static/chunks/0mq0uqbbbb1~2.js +0 -1
- package/.next/static/chunks/0neevhl_o1ozu.css +0 -2
- package/.next/static/chunks/0omj~p3uxkic-.js +0 -1
- package/.next/static/chunks/0t-gr6j-c65qb.js +0 -1
- package/.next/static/chunks/0t16ai99uv4j3.js +0 -1
- package/.next/static/chunks/0tcl81ybuob5i.js +0 -1
- package/.next/static/chunks/0uqimvsni_op~.js +0 -1
- package/.next/static/chunks/0wt3kws~_yr8z.js +0 -1
- package/.next/static/chunks/0xzdu87n_for1.js +0 -5
- package/.next/static/chunks/0y0tdl.rl6v1u.js +0 -1
- package/.next/static/chunks/0z2bzovqhl2f5.js +0 -1
- package/.next/static/chunks/0z4y0x1ifgy.e.js +0 -1
- package/.next/static/chunks/0z7bwntvfhxzi.js +0 -12
- package/.next/static/chunks/102ijqpvi0z-d.js +0 -1
- package/.next/static/chunks/10n23t.1hpb-1.js +0 -1
- package/.next/static/chunks/11_e3-j5gzbj4.js +0 -1
- package/.next/static/chunks/14.ims4y7osot.js +0 -1
- package/.next/static/chunks/14xzmrt5ly6gq.js +0 -31
- package/.next/static/chunks/151wr~6x8aclx.js +0 -1
- package/.next/static/chunks/169po6_~f3-d5.js +0 -1
- package/.next/static/chunks/16w-ap~msrwpj.js +0 -1
- package/.next/static/chunks/176n7f13ve~a9.js +0 -1
- package/.next/static/chunks/17oc2l.ekcs8b.css +0 -1
- package/.next/static/chunks/turbopack-0wjmrsi.z32s0.js +0 -1
- package/.next/static/media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2 +0 -0
- package/.next/static/media/7178b3e590c64307-s.11.cyxs5p-0z~.woff2 +0 -0
- package/.next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2 +0 -0
- package/.next/static/media/8a480f0b521d4e75-s.06d3mdzz5bre_.woff2 +0 -0
- package/.next/static/media/apple-icon.16aocl-s-v2qz.png +0 -0
- package/.next/static/media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2 +0 -0
- package/.next/static/media/caa3a2e1cccd8315-s.p.16t1db8_9y2o~.woff2 +0 -0
- package/.next/static/media/favicon.0y2d6j9cou~8p.ico +0 -0
- package/.next/static/media/icon0.0a6mkq6meyird.svg +0 -1
- package/.next/static/media/icon1.04ux133882seb.png +0 -0
- /package/{.next/standalone/tower/components.json → components.json} +0 -0
- /package/{.next/standalone/tower/postcss.config.mjs → postcss.config.mjs} +0 -0
- /package/{.next/standalone/tower/prisma → prisma}/prisma/dev.db +0 -0
- /package/{.next/standalone/tower/prisma → prisma}/seed.ts +0 -0
- /package/{.next/standalone/tower/docs/public → public}/banner.jpg +0 -0
- /package/{.next/standalone/tower/docs/public → public}/logo.png +0 -0
- /package/{.next/standalone/tower/public → public}/web-app-manifest-192x192.png +0 -0
- /package/{.next/standalone/tower/public → public}/web-app-manifest-512x512.png +0 -0
- /package/{.next/standalone/tower/scripts → scripts}/init-tower.ts +0 -0
- /package/{.next/standalone/tower/scripts → scripts}/post-tool-hook.js +0 -0
- /package/{.next/standalone/tower/scripts → scripts}/session-start-hook.js +0 -0
- /package/{.next/standalone/tower/scripts → scripts}/stop-hook.js +0 -0
- /package/{.next/standalone/tower/skills → skills}/tower/SKILL.md +0 -0
- /package/{.next/standalone/tower/src → src}/actions/agent-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/agent-config-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/ai-config-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/asset-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/assistant-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/cli-profile-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/config-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/file-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/git-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/label-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/note-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/onboarding-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/preview-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/project-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/prompt-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/report-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/search-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/search-code-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/task-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/actions/workspace-actions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/adapters/test/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/browse-fs/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/files/assets/[projectId]/[filename]/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/git/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/assets/[projectId]/[filename]/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/assets/reveal/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/assistant/chat/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/assistant/images/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/assistant/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/assistant/sessions/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/cache/[...segments]/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/hooks/install/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/hooks/session/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/hooks/stop/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/hooks/upload/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/notifications/pending/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/terminal/[taskId]/buffer/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/terminal/[taskId]/input/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/internal/terminal/[taskId]/start/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/tasks/[taskId]/diff/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/api/tasks/[taskId]/merge/route.ts +0 -0
- /package/{.next/standalone/tower/src → src}/app/apple-icon.png +0 -0
- /package/{.next/standalone/tower/docs/public → src/app}/favicon.ico +0 -0
- /package/{.next/standalone/tower/src → src}/app/globals.css +0 -0
- /package/{.next/standalone/tower/src → src}/app/icon0.svg +0 -0
- /package/{.next/standalone/tower/src → src}/app/icon1.png +0 -0
- /package/{.next/standalone/tower/src → src}/app/layout.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/manifest.json +0 -0
- /package/{.next/standalone/tower/src → src}/app/missions/missions-client.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/missions/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/onboarding/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/settings/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/archive/archive-page-client.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/archive/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/assets/assets-page-client.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/assets/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/board-page-client.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/notes/notes-page-client.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/notes/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/projects/[projectId]/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/tasks/[taskId]/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/[workspaceId]/tasks/[taskId]/task-page-client.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/app/workspaces/page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assets/asset-item.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assets/asset-list.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assets/asset-upload.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assets/image-lightbox.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assets/text-preview-dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assistant/assistant-chat-bubble.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assistant/assistant-chat.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assistant/assistant-panel.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assistant/assistant-provider.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assistant/image-preview-modal.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/assistant/image-thumbnail-strip.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/board/board-column.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/board/board-filters.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/board/board-stats.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/board/column-tasks-dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/board/create-task-dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/board/kanban-board.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/board/project-tabs.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/board/task-card-context-menu.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/board/task-card.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/layout/app-sidebar.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/layout/folder-browser-dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/layout/layout-client.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/layout/search-dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/layout/sub-page-nav.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/layout/top-bar.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/missions/grid-layout-presets.ts +0 -0
- /package/{.next/standalone/tower/src → src}/components/missions/grid-preset-picker.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/missions/merge-missions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/components/missions/mission-card.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/missions/task-picker-dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/notes/category-filter.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/notes/note-card.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/notes/note-editor.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/notes/note-list.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/notifications/notification-permission-banner.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/notifications/use-notification-listener.ts +0 -0
- /package/{.next/standalone/tower/src → src}/components/onboarding/guided-tour.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/onboarding/onboarding-wizard.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/onboarding/wizard-step-cli.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/onboarding/wizard-step-username.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/project/create-project-dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/project/import-project-dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/providers/theme-provider.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/repository/create-branch-dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/repository/git-changes-panel.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/repository/git-log-panel.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/repository/git-stash-panel.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/repository/repo-sidebar.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/settings/cli-adapter-tester.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/settings/settings-page.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/code-editor.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/code-search.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/diff-editor.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/editor-git-panel.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/editor-tabs.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/execution-timeline.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/file-tree-context-menu.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/file-tree-node.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/file-tree.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/preview-panel.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/task-detail-panel.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/task-diff-view.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/task-file-changes.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/task-merge-confirm-dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/task-metadata.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/task-notes-panel.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/task-overview-drawer.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/task-terminal.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/terminal-portal.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/task/types.ts +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/avatar.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/badge.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/button.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/card.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/command.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/dialog.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/dropdown-menu.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/empty-state.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/error-boundary.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/input-group.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/input.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/label.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/popover.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/scroll-area.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/segmented-control.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/select.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/separator.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/sheet.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/sonner.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/switch.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/tabs.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/textarea.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/toast.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/components/ui/tooltip.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/hooks/sse-event-reducer.ts +0 -0
- /package/{.next/standalone/tower/src → src}/hooks/use-assistant-chat.ts +0 -0
- /package/{.next/standalone/tower/src → src}/hooks/use-image-upload.ts +0 -0
- /package/{.next/standalone/tower/src → src}/instrumentation.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/ai/adapters/cli/claude-cli-adapter.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/ai/capability-resolver.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/ai/provider-registry.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/ai/providers/claude.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/ai/providers/index.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/ai/types.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/assistant-constants.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/assistant-message-converter.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/assistant-sessions.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/build-multimodal-prompt.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/claude-session.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/cli-test.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/config-defaults.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/config-reader.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/constants.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/db.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/diff-parser.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/execution-summary.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/file-serve-client.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/file-serve.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/file-utils.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/fs-security.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/fts.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/git-api.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/git-url.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/i18n/en.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/i18n/types.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/i18n/zh.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/i18n.tsx +0 -0
- /package/{.next/standalone/tower/src → src}/lib/init-tower.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/instrumentation-tasks.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/internal-api-guard.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/logger.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/mime-magic.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/platform.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/preview-process.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/pty/pty-session.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/pty/session-store.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/pty/ws-server.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/schemas.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/search.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/terminal-link-provider.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/tower-dir.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/utils.ts +0 -0
- /package/{.next/standalone/tower/src → src}/lib/worktree.ts +0 -0
- /package/{.next/standalone/tower/src → src}/stores/board-store.ts +0 -0
- /package/{.next/standalone/tower/src → src}/stores/task-execution-store.ts +0 -0
- /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
|
-
```
|